Skip to content

Commit ae64d77

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 e75e00d commit ae64d77

File tree

1 file changed

+85
-2
lines changed

1 file changed

+85
-2
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,6 @@ use crate::offers::offer::{Offer, OfferBuilder};
7474
use crate::offers::parse::Bolt12SemanticError;
7575
use crate::offers::refund::{Refund, RefundBuilder};
7676
use crate::offers::signer;
77-
#[cfg(async_payments)]
78-
use crate::offers::static_invoice::StaticInvoice;
7977
use crate::onion_message::async_payments::{AsyncPaymentsMessage, HeldHtlcAvailable, ReleaseHeldHtlc, AsyncPaymentsMessageHandler};
8078
use crate::onion_message::dns_resolution::HumanReadableName;
8179
use crate::onion_message::messenger::{Destination, MessageRouter, Responder, ResponseInstruction, MessageSendInstructions};
@@ -90,6 +88,11 @@ use crate::util::ser::{BigSize, FixedLengthReader, Readable, ReadableArgs, Maybe
9088
use crate::util::ser::TransactionU16LenLimited;
9189
use crate::util::logger::{Level, Logger, WithContext};
9290
use crate::util::errors::APIError;
91+
#[cfg(async_payments)] use {
92+
crate::blinded_path::payment::AsyncBolt12OfferContext,
93+
crate::offers::offer::Amount,
94+
crate::offers::static_invoice::{DEFAULT_RELATIVE_EXPIRY as STATIC_INVOICE_DEFAULT_RELATIVE_EXPIRY, StaticInvoice, StaticInvoiceBuilder},
95+
};
9396

9497
#[cfg(feature = "dnssec")]
9598
use crate::blinded_path::message::DNSResolverContext;
@@ -9958,6 +9961,86 @@ where
99589961
#[cfg(c_bindings)]
99599962
create_refund_builder!(self, RefundMaybeWithDerivedMetadataBuilder);
99609963

9964+
/// Create an offer for receiving async payments as an often-offline recipient.
9965+
///
9966+
/// Because we may be offline when the payer attempts to request an invoice, you MUST:
9967+
/// 1. Provide at least 1 [`BlindedMessagePath`] terminating at an always-online node that will
9968+
/// serve the [`StaticInvoice`] created from this offer on our behalf.
9969+
/// 2. Use [`Self::create_static_invoice_builder`] to create a [`StaticInvoice`] from this
9970+
/// [`Offer`] plus the returned [`Nonce`], and provide the static invoice to the
9971+
/// aforementioned always-online node.
9972+
#[cfg(async_payments)]
9973+
pub fn create_async_receive_offer_builder(
9974+
&self, message_paths_to_always_online_node: Vec<BlindedMessagePath>
9975+
) -> Result<(OfferBuilder<DerivedMetadata, secp256k1::All>, Nonce), Bolt12SemanticError> {
9976+
if message_paths_to_always_online_node.is_empty() {
9977+
return Err(Bolt12SemanticError::MissingPaths)
9978+
}
9979+
9980+
let node_id = self.get_our_node_id();
9981+
let expanded_key = &self.inbound_payment_key;
9982+
let entropy = &*self.entropy_source;
9983+
let secp_ctx = &self.secp_ctx;
9984+
9985+
let nonce = Nonce::from_entropy_source(entropy);
9986+
let mut builder = OfferBuilder::deriving_signing_pubkey(
9987+
node_id, expanded_key, nonce, secp_ctx
9988+
).chain_hash(self.chain_hash);
9989+
9990+
for path in message_paths_to_always_online_node {
9991+
builder = builder.path(path);
9992+
}
9993+
9994+
Ok((builder.into(), nonce))
9995+
}
9996+
9997+
/// Creates a [`StaticInvoiceBuilder`] from the corresponding [`Offer`] and [`Nonce`] that were
9998+
/// created via [`Self::create_async_receive_offer_builder`]. If `relative_expiry` is unset, the
9999+
/// invoice's expiry will default to [`STATIC_INVOICE_DEFAULT_RELATIVE_EXPIRY`].
10000+
#[cfg(async_payments)]
10001+
pub fn create_static_invoice_builder<'a>(
10002+
&self, offer: &'a Offer, offer_nonce: Nonce, relative_expiry: Option<Duration>
10003+
) -> Result<StaticInvoiceBuilder<'a>, Bolt12SemanticError> {
10004+
let expanded_key = &self.inbound_payment_key;
10005+
let entropy = &*self.entropy_source;
10006+
let secp_ctx = &self.secp_ctx;
10007+
10008+
let payment_context = PaymentContext::AsyncBolt12Offer(
10009+
AsyncBolt12OfferContext { offer_nonce }
10010+
);
10011+
let amount_msat = offer.amount().and_then(|amount| {
10012+
match amount {
10013+
Amount::Bitcoin { amount_msats } => Some(amount_msats),
10014+
Amount::Currency { .. } => None
10015+
}
10016+
});
10017+
10018+
let relative_expiry = relative_expiry.unwrap_or(STATIC_INVOICE_DEFAULT_RELATIVE_EXPIRY);
10019+
let relative_expiry_secs: u32 = relative_expiry.as_secs().try_into().unwrap_or(u32::MAX);
10020+
10021+
let created_at = self.duration_since_epoch();
10022+
let payment_secret = inbound_payment::create_for_spontaneous_payment(
10023+
&self.inbound_payment_key, amount_msat, relative_expiry_secs, created_at.as_secs(), None
10024+
).map_err(|()| Bolt12SemanticError::InvalidAmount)?;
10025+
10026+
let payment_paths = self.create_blinded_payment_paths(
10027+
amount_msat, payment_secret, payment_context, relative_expiry_secs
10028+
).map_err(|()| Bolt12SemanticError::MissingPaths)?;
10029+
10030+
let nonce = Nonce::from_entropy_source(entropy);
10031+
let hmac = signer::hmac_for_held_htlc_available_context(nonce, expanded_key);
10032+
let context = MessageContext::AsyncPayments(
10033+
AsyncPaymentsContext::InboundPayment { nonce, hmac }
10034+
);
10035+
let async_receive_message_paths = self.create_blinded_paths(context)
10036+
.map_err(|()| Bolt12SemanticError::MissingPaths)?;
10037+
10038+
StaticInvoiceBuilder::for_offer_using_derived_keys(
10039+
offer, payment_paths, async_receive_message_paths, created_at, expanded_key,
10040+
offer_nonce, secp_ctx
10041+
).map(|inv| inv.allow_mpp().relative_expiry(relative_expiry_secs))
10042+
}
10043+
996110044
/// Pays for an [`Offer`] using the given parameters by creating an [`InvoiceRequest`] and
996210045
/// enqueuing it to be sent via an onion message. [`ChannelManager`] will pay the actual
996310046
/// [`Bolt12Invoice`] once it is received.

0 commit comments

Comments
 (0)