Skip to content

Commit eae699b

Browse files
valentinewallacetnull
authored andcommitted
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 53c9969 commit eae699b

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
@@ -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;
@@ -9949,6 +9952,86 @@ where
99499952
#[cfg(c_bindings)]
99509953
create_refund_builder!(self, RefundMaybeWithDerivedMetadataBuilder);
99519954

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

0 commit comments

Comments
 (0)