Skip to content

Commit 3c6896c

Browse files
committed
Add a BOLT11 invoice utility to ChannelManager
Now that the lightning crate depends on the lightning_invoice crate, the utility functions previously living in the latter can be implemented on ChannelManager. Additionally, the parameters are now moved to a struct in order to remove the increasingly combinatorial blow-up of methods. The new Bolt11InvoiceParameters is used to determine what values to set in the invoice. Using None for any given parameter results in a reasonable the default or a behavior determined by the ChannelManager as detailed in the documentation.
1 parent 284cb28 commit 3c6896c

File tree

2 files changed

+195
-184
lines changed

2 files changed

+195
-184
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 142 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ use {
102102
crate::offers::refund::RefundMaybeWithDerivedMetadataBuilder,
103103
};
104104

105+
use lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription, CreationError, Currency, Description, InvoiceBuilder as Bolt11InvoiceBuilder, SignOrCreationError, DEFAULT_EXPIRY_TIME};
106+
105107
use alloc::collections::{btree_map, BTreeMap};
106108

107109
use crate::io;
@@ -2199,7 +2201,7 @@ where
21992201
L::Target: Logger,
22002202
{
22012203
default_configuration: UserConfig,
2202-
pub(super) chain_hash: ChainHash,
2204+
chain_hash: ChainHash,
22032205
fee_estimator: LowerBoundedFeeEstimator<F>,
22042206
chain_monitor: M,
22052207
tx_broadcaster: T,
@@ -9093,6 +9095,145 @@ where
90939095
self.finish_close_channel(failure);
90949096
}
90959097
}
9098+
9099+
/// Utility for creating a BOLT11 invoice that can be verified by [`ChannelManager`] without
9100+
/// storing any additional state. It achieves this by including a [`PaymentSecret`] in the
9101+
/// invoice which it uses to verify that the invoice has not expired and the payment amount is
9102+
/// sufficient, reproducing the [`PaymentPreimage`] if applicable.
9103+
pub fn create_bolt11_invoice(
9104+
&self, params: Bolt11InvoiceParameters,
9105+
) -> Result<Bolt11Invoice, SignOrCreationError<()>> {
9106+
let Bolt11InvoiceParameters {
9107+
amount_msats, description, invoice_expiry_delta_secs, min_final_cltv_expiry_delta,
9108+
payment_hash,
9109+
} = params;
9110+
9111+
let currency =
9112+
Network::from_chain_hash(self.chain_hash).map(Into::into).unwrap_or(Currency::Bitcoin);
9113+
9114+
#[cfg(feature = "std")]
9115+
let duration_since_epoch = {
9116+
use std::time::SystemTime;
9117+
SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)
9118+
.expect("for the foreseeable future this shouldn't happen")
9119+
};
9120+
#[cfg(not(feature = "std"))]
9121+
let duration_since_epoch =
9122+
Duration::from_secs(self.highest_seen_timestamp.load(Ordering::Acquire) as u64);
9123+
9124+
if let Some(min_final_cltv_expiry_delta) = min_final_cltv_expiry_delta {
9125+
if min_final_cltv_expiry_delta.saturating_add(3) < MIN_FINAL_CLTV_EXPIRY_DELTA {
9126+
return Err(SignOrCreationError::CreationError(CreationError::MinFinalCltvExpiryDeltaTooShort));
9127+
}
9128+
}
9129+
9130+
let (payment_hash, payment_secret) = match payment_hash {
9131+
Some(payment_hash) => {
9132+
let payment_secret = self
9133+
.create_inbound_payment_for_hash(
9134+
payment_hash, amount_msats,
9135+
invoice_expiry_delta_secs.unwrap_or(DEFAULT_EXPIRY_TIME as u32),
9136+
min_final_cltv_expiry_delta,
9137+
)
9138+
.map_err(|()| SignOrCreationError::CreationError(CreationError::InvalidAmount))?;
9139+
(payment_hash, payment_secret)
9140+
},
9141+
None => {
9142+
self
9143+
.create_inbound_payment(
9144+
amount_msats, invoice_expiry_delta_secs.unwrap_or(DEFAULT_EXPIRY_TIME as u32),
9145+
min_final_cltv_expiry_delta,
9146+
)
9147+
.map_err(|()| SignOrCreationError::CreationError(CreationError::InvalidAmount))?
9148+
},
9149+
};
9150+
9151+
log_trace!(self.logger, "Creating invoice with payment hash {}", &payment_hash);
9152+
9153+
let invoice = Bolt11InvoiceBuilder::new(currency);
9154+
let invoice = match description {
9155+
Bolt11InvoiceDescription::Direct(description) => invoice.description(description.into_inner().0),
9156+
Bolt11InvoiceDescription::Hash(hash) => invoice.description_hash(hash.0),
9157+
};
9158+
9159+
let mut invoice = invoice
9160+
.duration_since_epoch(duration_since_epoch)
9161+
.payee_pub_key(self.get_our_node_id())
9162+
.payment_hash(Hash::from_slice(&payment_hash.0).unwrap())
9163+
.payment_secret(payment_secret)
9164+
.basic_mpp()
9165+
.min_final_cltv_expiry_delta(
9166+
// Add a buffer of 3 to the delta if present, otherwise use LDK's minimum.
9167+
min_final_cltv_expiry_delta.map(|x| x.saturating_add(3)).unwrap_or(MIN_FINAL_CLTV_EXPIRY_DELTA).into()
9168+
);
9169+
9170+
if let Some(invoice_expiry_delta_secs) = invoice_expiry_delta_secs{
9171+
invoice = invoice.expiry_time(Duration::from_secs(invoice_expiry_delta_secs.into()));
9172+
}
9173+
9174+
if let Some(amount_msats) = amount_msats {
9175+
invoice = invoice.amount_milli_satoshis(amount_msats);
9176+
}
9177+
9178+
let channels = self.list_channels();
9179+
let route_hints = super::invoice_utils::sort_and_filter_channels(channels, amount_msats, &self.logger);
9180+
for hint in route_hints {
9181+
invoice = invoice.private_route(hint);
9182+
}
9183+
9184+
let raw_invoice = invoice.build_raw().map_err(|e| SignOrCreationError::CreationError(e))?;
9185+
let signature = self.node_signer.sign_invoice(&raw_invoice, Recipient::Node);
9186+
9187+
raw_invoice
9188+
.sign(|_| signature)
9189+
.map(|invoice| Bolt11Invoice::from_signed(invoice).unwrap())
9190+
.map_err(|e| SignOrCreationError::SignError(e))
9191+
}
9192+
}
9193+
9194+
/// Parameters used with [`create_bolt11_invoice`].
9195+
///
9196+
/// [`create_bolt11_invoice`]: ChannelManager::create_bolt11_invoice
9197+
pub struct Bolt11InvoiceParameters {
9198+
/// The amount for the invoice, if any.
9199+
pub amount_msats: Option<u64>,
9200+
9201+
/// The description for what the invoice is for, or hash of such description.
9202+
pub description: Bolt11InvoiceDescription,
9203+
9204+
/// The invoice expiration relative to its creation time. If not set, the invoice will expire in
9205+
/// [`DEFAULT_EXPIRY_TIME`] by default.
9206+
///
9207+
/// The creation time used is the duration since the Unix epoch for `std` builds. For non-`std`
9208+
/// builds, the highest block timestamp seen is used instead.
9209+
pub invoice_expiry_delta_secs: Option<u32>,
9210+
9211+
/// The minimum `cltv_expiry` for the last HTLC in the route. If not set, will use
9212+
/// [`MIN_FINAL_CLTV_EXPIRY_DELTA`].
9213+
///
9214+
/// If set, must be at least [`MIN_FINAL_CLTV_EXPIRY_DELTA`], and a three-block buffer will be
9215+
/// added as well to allow for up to a few new block confirmations during routing.
9216+
pub min_final_cltv_expiry_delta: Option<u16>,
9217+
9218+
/// The payment hash used in the invoice. If not set, a payment hash will be generated using a
9219+
/// preimage that can be reproduced by [`ChannelManager`] without storing any state.
9220+
///
9221+
/// Uses the payment hash if set. This may be useful if you're building an on-chain swap or
9222+
/// involving another protocol where the payment hash is also involved outside the scope of
9223+
/// lightning.
9224+
pub payment_hash: Option<PaymentHash>,
9225+
}
9226+
9227+
impl Default for Bolt11InvoiceParameters {
9228+
fn default() -> Self {
9229+
Self {
9230+
amount_msats: None,
9231+
description: Bolt11InvoiceDescription::Direct(Description::empty()),
9232+
invoice_expiry_delta_secs: None,
9233+
min_final_cltv_expiry_delta: None,
9234+
payment_hash: None,
9235+
}
9236+
}
90969237
}
90979238

90989239
macro_rules! create_offer_builder { ($self: ident, $builder: ty) => {

0 commit comments

Comments
 (0)