Skip to content

Commit 5701ecb

Browse files
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 96db8aa commit 5701ecb

File tree

1 file changed

+85
-2
lines changed

1 file changed

+85
-2
lines changed

Diff for: 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};
@@ -88,6 +86,11 @@ use crate::util::ser::{BigSize, FixedLengthReader, Readable, ReadableArgs, Maybe
8886
use crate::util::ser::TransactionU16LenLimited;
8987
use crate::util::logger::{Level, Logger, WithContext};
9088
use crate::util::errors::APIError;
89+
#[cfg(async_payments)] use {
90+
crate::blinded_path::payment::AsyncBolt12OfferContext,
91+
crate::offers::offer::Amount,
92+
crate::offers::static_invoice::{DEFAULT_RELATIVE_EXPIRY as STATIC_INVOICE_DEFAULT_RELATIVE_EXPIRY, StaticInvoice, StaticInvoiceBuilder},
93+
};
9194

9295
#[cfg(feature = "dnssec")]
9396
use crate::blinded_path::message::DNSResolverContext;
@@ -9988,6 +9991,86 @@ where
99889991
#[cfg(c_bindings)]
99899992
create_refund_builder!(self, RefundMaybeWithDerivedMetadataBuilder);
99909993

9994+
/// Create an offer for receiving async payments as an often-offline recipient.
9995+
///
9996+
/// Because we may be offline when the payer attempts to request an invoice, you MUST:
9997+
/// 1. Provide at least 1 [`BlindedMessagePath`] terminating at an always-online node that will
9998+
/// serve the [`StaticInvoice`] created from this offer on our behalf.
9999+
/// 2. Use [`Self::create_static_invoice_builder`] to create a [`StaticInvoice`] from this
10000+
/// [`Offer`] plus the returned [`Nonce`], and provide the static invoice to the
10001+
/// aforementioned always-online node.
10002+
#[cfg(async_payments)]
10003+
pub fn create_async_receive_offer_builder(
10004+
&self, message_paths_to_always_online_node: Vec<BlindedMessagePath>
10005+
) -> Result<(OfferBuilder<DerivedMetadata, secp256k1::All>, Nonce), Bolt12SemanticError> {
10006+
if message_paths_to_always_online_node.is_empty() {
10007+
return Err(Bolt12SemanticError::MissingPaths)
10008+
}
10009+
10010+
let node_id = self.get_our_node_id();
10011+
let expanded_key = &self.inbound_payment_key;
10012+
let entropy = &*self.entropy_source;
10013+
let secp_ctx = &self.secp_ctx;
10014+
10015+
let nonce = Nonce::from_entropy_source(entropy);
10016+
let mut builder = OfferBuilder::deriving_signing_pubkey(
10017+
node_id, expanded_key, nonce, secp_ctx
10018+
).chain_hash(self.chain_hash);
10019+
10020+
for path in message_paths_to_always_online_node {
10021+
builder = builder.path(path);
10022+
}
10023+
10024+
Ok((builder.into(), nonce))
10025+
}
10026+
10027+
/// Creates a [`StaticInvoiceBuilder`] from the corresponding [`Offer`] and [`Nonce`] that were
10028+
/// created via [`Self::create_async_receive_offer_builder`]. If `relative_expiry` is unset, the
10029+
/// invoice's expiry will default to [`STATIC_INVOICE_DEFAULT_RELATIVE_EXPIRY`].
10030+
#[cfg(async_payments)]
10031+
pub fn create_static_invoice_builder<'a>(
10032+
&self, offer: &'a Offer, offer_nonce: Nonce, relative_expiry: Option<Duration>
10033+
) -> Result<StaticInvoiceBuilder<'a>, Bolt12SemanticError> {
10034+
let expanded_key = &self.inbound_payment_key;
10035+
let entropy = &*self.entropy_source;
10036+
let secp_ctx = &self.secp_ctx;
10037+
10038+
let payment_context = PaymentContext::AsyncBolt12Offer(
10039+
AsyncBolt12OfferContext { offer_nonce }
10040+
);
10041+
let amount_msat = offer.amount().and_then(|amount| {
10042+
match amount {
10043+
Amount::Bitcoin { amount_msats } => Some(amount_msats),
10044+
Amount::Currency { .. } => None
10045+
}
10046+
});
10047+
10048+
let relative_expiry = relative_expiry.unwrap_or(STATIC_INVOICE_DEFAULT_RELATIVE_EXPIRY);
10049+
let relative_expiry_secs: u32 = relative_expiry.as_secs().try_into().unwrap_or(u32::MAX);
10050+
10051+
let created_at = self.duration_since_epoch();
10052+
let payment_secret = inbound_payment::create_for_spontaneous_payment(
10053+
&self.inbound_payment_key, amount_msat, relative_expiry_secs, created_at.as_secs(), None
10054+
).map_err(|()| Bolt12SemanticError::InvalidAmount)?;
10055+
10056+
let payment_paths = self.create_blinded_payment_paths(
10057+
amount_msat, payment_secret, payment_context, relative_expiry_secs
10058+
).map_err(|()| Bolt12SemanticError::MissingPaths)?;
10059+
10060+
let nonce = Nonce::from_entropy_source(entropy);
10061+
let hmac = signer::hmac_for_held_htlc_available_context(nonce, expanded_key);
10062+
let context = MessageContext::AsyncPayments(
10063+
AsyncPaymentsContext::InboundPayment { nonce, hmac }
10064+
);
10065+
let async_receive_message_paths = self.create_blinded_paths(context)
10066+
.map_err(|()| Bolt12SemanticError::MissingPaths)?;
10067+
10068+
StaticInvoiceBuilder::for_offer_using_derived_keys(
10069+
offer, payment_paths, async_receive_message_paths, created_at, expanded_key,
10070+
offer_nonce, secp_ctx
10071+
)
10072+
}
10073+
999110074
/// Pays for an [`Offer`] using the given parameters by creating an [`InvoiceRequest`] and
999210075
/// enqueuing it to be sent via an onion message. [`ChannelManager`] will pay the actual
999310076
/// [`Bolt12Invoice`] once it is received.

0 commit comments

Comments
 (0)