Skip to content

Commit 1092336

Browse files
Add utils to create static invoices and their corresponding offers
Static invoices are intended to be provided on the often-offline recipient's behalf by another node, see new utils' docs and BOLTs PR 1149.
1 parent 11c8f49 commit 1092336

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;
@@ -9549,6 +9552,86 @@ where
95499552
#[cfg(c_bindings)]
95509553
create_refund_builder!(self, RefundMaybeWithDerivedMetadataBuilder);
95519554

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

0 commit comments

Comments
 (0)