Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 103e012

Browse files
committedNov 27, 2024
Add utils to create static invoices and their corresponding offers
We can't use our regular offer creation util for receiving async payments because the recipient can't be relied on to be online to service invoice_requests. Therefore, add a new offer creation util that is parameterized by blinded message paths to another node on the network that *is* always-online and can serve static invoices on behalf of the often-offline recipient. Also add a utility for creating static invoices corresponding to these offers. See new utils' docs and BOLTs PR 1149 for more info.
1 parent d5431f4 commit 103e012

File tree

1 file changed

+85
-2
lines changed

1 file changed

+85
-2
lines changed
 

‎lightning/src/ln/channelmanager.rs

+85-2
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,6 @@ use crate::offers::offer::{Offer, OfferBuilder};
7272
use crate::offers::parse::Bolt12SemanticError;
7373
use crate::offers::refund::{Refund, RefundBuilder};
7474
use crate::offers::signer;
75-
#[cfg(async_payments)]
76-
use crate::offers::static_invoice::StaticInvoice;
7775
use crate::onion_message::async_payments::{AsyncPaymentsMessage, HeldHtlcAvailable, ReleaseHeldHtlc, AsyncPaymentsMessageHandler};
7876
use crate::onion_message::dns_resolution::HumanReadableName;
7977
use crate::onion_message::messenger::{Destination, MessageRouter, Responder, ResponseInstruction, MessageSendInstructions};
@@ -87,6 +85,11 @@ use crate::util::string::UntrustedString;
8785
use crate::util::ser::{BigSize, FixedLengthReader, Readable, ReadableArgs, MaybeReadable, Writeable, Writer, VecWriter};
8886
use crate::util::logger::{Level, Logger, WithContext};
8987
use crate::util::errors::APIError;
88+
#[cfg(async_payments)] use {
89+
crate::blinded_path::payment::AsyncBolt12OfferContext,
90+
crate::offers::offer::Amount,
91+
crate::offers::static_invoice::{DEFAULT_RELATIVE_EXPIRY as STATIC_INVOICE_DEFAULT_RELATIVE_EXPIRY, StaticInvoice, StaticInvoiceBuilder},
92+
};
9093

9194
#[cfg(feature = "dnssec")]
9295
use crate::blinded_path::message::DNSResolverContext;
@@ -9554,6 +9557,86 @@ where
95549557
#[cfg(c_bindings)]
95559558
create_refund_builder!(self, RefundMaybeWithDerivedMetadataBuilder);
95569559

9560+
/// Create an offer for receiving async payments as an often-offline recipient.
9561+
///
9562+
/// Because we may be offline when the payer attempts to request an invoice, you MUST:
9563+
/// 1. Provide at least 1 [`BlindedPath`] for onion messages terminating at an always-online node
9564+
/// that will serve the [`StaticInvoice`] created from this offer on our behalf.
9565+
/// 2. Use [`Self::create_static_invoice_builder_for_async_receive_offer`] to create a
9566+
/// [`StaticInvoice`] from this [`Offer`] plus the returned [`Nonce`], and provide the static
9567+
/// invoice to the aforementioned always-online node.
9568+
#[cfg(async_payments)]
9569+
pub fn create_async_receive_offer_builder(
9570+
&self, message_paths_to_always_online_node: Vec<BlindedMessagePath>
9571+
) -> Result<(OfferBuilder<DerivedMetadata, secp256k1::All>, Nonce), Bolt12SemanticError> {
9572+
if message_paths_to_always_online_node.is_empty() {
9573+
return Err(Bolt12SemanticError::MissingPaths)
9574+
}
9575+
9576+
let node_id = self.get_our_node_id();
9577+
let expanded_key = &self.inbound_payment_key;
9578+
let entropy = &*self.entropy_source;
9579+
let secp_ctx = &self.secp_ctx;
9580+
9581+
let nonce = Nonce::from_entropy_source(entropy);
9582+
let mut builder = OfferBuilder::deriving_signing_pubkey(
9583+
node_id, expanded_key, nonce, secp_ctx
9584+
).chain_hash(self.chain_hash);
9585+
9586+
for path in message_paths_to_always_online_node {
9587+
builder = builder.path(path);
9588+
}
9589+
9590+
Ok((builder.into(), nonce))
9591+
}
9592+
9593+
/// Creates a [`StaticInvoiceBuilder`] from the corresponding [`Offer`] and [`Nonce`] that were
9594+
/// created via [`Self::create_async_receive_offer_builder`]. If `relative_expiry` is unset, the
9595+
/// invoice's expiry will default to [`STATIC_INVOICE_DEFAULT_RELATIVE_EXPIRY`].
9596+
#[cfg(async_payments)]
9597+
pub fn create_static_invoice_builder_for_async_receive_offer<'a>(
9598+
&self, offer: &'a Offer, offer_nonce: Nonce, relative_expiry: Option<Duration>
9599+
) -> Result<StaticInvoiceBuilder<'a>, Bolt12SemanticError> {
9600+
let expanded_key = &self.inbound_payment_key;
9601+
let entropy = &*self.entropy_source;
9602+
let secp_ctx = &self.secp_ctx;
9603+
9604+
let payment_context = PaymentContext::AsyncBolt12Offer(
9605+
AsyncBolt12OfferContext { offer_id: offer.id(), offer_nonce }
9606+
);
9607+
let amount_msat = offer.amount().and_then(|amount| {
9608+
match amount {
9609+
Amount::Bitcoin { amount_msats } => Some(amount_msats),
9610+
Amount::Currency { .. } => None
9611+
}
9612+
});
9613+
9614+
let relative_expiry = relative_expiry.unwrap_or(STATIC_INVOICE_DEFAULT_RELATIVE_EXPIRY);
9615+
let relative_expiry_secs: u32 = relative_expiry.as_secs().try_into().unwrap_or(u32::MAX);
9616+
9617+
let created_at = self.duration_since_epoch();
9618+
let payment_secret = inbound_payment::create_for_spontaneous_payment(
9619+
&self.inbound_payment_key, amount_msat, relative_expiry_secs, created_at.as_secs(), None
9620+
).map_err(|()| Bolt12SemanticError::InvalidAmount)?;
9621+
9622+
let payment_paths = self.create_blinded_payment_paths(
9623+
amount_msat, payment_secret, payment_context, relative_expiry_secs
9624+
).map_err(|()| Bolt12SemanticError::MissingPaths)?;
9625+
9626+
let nonce = Nonce::from_entropy_source(entropy);
9627+
let hmac = offer.id().hmac_for_static_invoice(nonce, expanded_key);
9628+
let context = MessageContext::AsyncPayments(
9629+
AsyncPaymentsContext::InboundPayment { offer_id: offer.id(), nonce, hmac }
9630+
);
9631+
let async_receive_message_paths = self.create_blinded_paths(context)
9632+
.map_err(|()| Bolt12SemanticError::MissingPaths)?;
9633+
9634+
StaticInvoiceBuilder::for_offer_using_derived_keys(
9635+
offer, payment_paths, async_receive_message_paths, created_at, expanded_key,
9636+
offer_nonce, secp_ctx
9637+
)
9638+
}
9639+
95579640
/// Pays for an [`Offer`] using the given parameters by creating an [`InvoiceRequest`] and
95589641
/// enqueuing it to be sent via an onion message. [`ChannelManager`] will pay the actual
95599642
/// [`Bolt12Invoice`] once it is received.

0 commit comments

Comments
 (0)
Please sign in to comment.