Skip to content

Commit 739c412

Browse files
authored
Merge pull request #3389 from jkczyz/2024-10-bolt11-invoice-utils
Add `ChannelManager::create_bolt11_invoice`
2 parents 5718baa + 7878801 commit 739c412

File tree

3 files changed

+370
-283
lines changed

3 files changed

+370
-283
lines changed

lightning-invoice/src/lib.rs

+34-9
Original file line numberDiff line numberDiff line change
@@ -233,25 +233,45 @@ pub struct Bolt11Invoice {
233233
signed_invoice: SignedRawBolt11Invoice,
234234
}
235235

236+
/// Represents the description of an invoice which has to be either a directly included string or
237+
/// a hash of a description provided out of band.
238+
#[derive(Eq, PartialEq, Debug, Clone, Ord, PartialOrd)]
239+
pub enum Bolt11InvoiceDescription {
240+
/// Description of what the invoice is for
241+
Direct(Description),
242+
243+
/// Hash of the description of what the invoice is for
244+
Hash(Sha256),
245+
}
246+
247+
impl Display for Bolt11InvoiceDescription {
248+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
249+
match self {
250+
Bolt11InvoiceDescription::Direct(desc) => write!(f, "{}", desc.0),
251+
Bolt11InvoiceDescription::Hash(hash) => write!(f, "{}", hash.0),
252+
}
253+
}
254+
}
255+
236256
/// Represents the description of an invoice which has to be either a directly included string or
237257
/// a hash of a description provided out of band.
238258
///
239259
/// This is not exported to bindings users as we don't have a good way to map the reference lifetimes making this
240260
/// practically impossible to use safely in languages like C.
241261
#[derive(Eq, PartialEq, Debug, Clone, Ord, PartialOrd)]
242-
pub enum Bolt11InvoiceDescription<'f> {
262+
pub enum Bolt11InvoiceDescriptionRef<'f> {
243263
/// Reference to the directly supplied description in the invoice
244264
Direct(&'f Description),
245265

246266
/// Reference to the description's hash included in the invoice
247267
Hash(&'f Sha256),
248268
}
249269

250-
impl<'f> Display for Bolt11InvoiceDescription<'f> {
270+
impl<'f> Display for Bolt11InvoiceDescriptionRef<'f> {
251271
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
252272
match self {
253-
Bolt11InvoiceDescription::Direct(desc) => write!(f, "{}", desc.0),
254-
Bolt11InvoiceDescription::Hash(hash) => write!(f, "{}", hash.0),
273+
Bolt11InvoiceDescriptionRef::Direct(desc) => write!(f, "{}", desc.0),
274+
Bolt11InvoiceDescriptionRef::Hash(hash) => write!(f, "{}", hash.0),
255275
}
256276
}
257277
}
@@ -708,7 +728,7 @@ impl<H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBui
708728
pub fn invoice_description(self, description: Bolt11InvoiceDescription) -> InvoiceBuilder<tb::True, H, T, C, S, M> {
709729
match description {
710730
Bolt11InvoiceDescription::Direct(desc) => {
711-
self.description(desc.clone().into_inner().0)
731+
self.description(desc.0.0)
712732
}
713733
Bolt11InvoiceDescription::Hash(hash) => {
714734
self.description_hash(hash.0)
@@ -1374,11 +1394,11 @@ impl Bolt11Invoice {
13741394
/// Return the description or a hash of it for longer ones
13751395
///
13761396
/// This is not exported to bindings users because we don't yet export Bolt11InvoiceDescription
1377-
pub fn description(&self) -> Bolt11InvoiceDescription {
1397+
pub fn description(&self) -> Bolt11InvoiceDescriptionRef {
13781398
if let Some(direct) = self.signed_invoice.description() {
1379-
return Bolt11InvoiceDescription::Direct(direct);
1399+
return Bolt11InvoiceDescriptionRef::Direct(direct);
13801400
} else if let Some(hash) = self.signed_invoice.description_hash() {
1381-
return Bolt11InvoiceDescription::Hash(hash);
1401+
return Bolt11InvoiceDescriptionRef::Hash(hash);
13821402
}
13831403
unreachable!("ensured by constructor");
13841404
}
@@ -1580,6 +1600,11 @@ impl Description {
15801600
}
15811601
}
15821602

1603+
/// Creates an empty `Description`.
1604+
pub fn empty() -> Self {
1605+
Description(UntrustedString(String::new()))
1606+
}
1607+
15831608
/// Returns the underlying description [`UntrustedString`]
15841609
pub fn into_inner(self) -> UntrustedString {
15851610
self.0
@@ -2211,7 +2236,7 @@ mod test {
22112236
assert_eq!(invoice.private_routes(), vec![&PrivateRoute(route_1), &PrivateRoute(route_2)]);
22122237
assert_eq!(
22132238
invoice.description(),
2214-
Bolt11InvoiceDescription::Hash(&Sha256(sha256::Hash::from_slice(&[3;32][..]).unwrap()))
2239+
Bolt11InvoiceDescriptionRef::Hash(&Sha256(sha256::Hash::from_slice(&[3;32][..]).unwrap()))
22152240
);
22162241
assert_eq!(invoice.payment_hash(), &sha256::Hash::from_slice(&[21;32][..]).unwrap());
22172242
assert_eq!(invoice.payment_secret(), &PaymentSecret([42; 32]));

lightning/src/ln/channelmanager.rs

+164-19
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;
@@ -1857,56 +1859,58 @@ where
18571859
///
18581860
/// ## BOLT 11 Invoices
18591861
///
1860-
/// The [`lightning-invoice`] crate is useful for creating BOLT 11 invoices. Specifically, use the
1861-
/// functions in its `utils` module for constructing invoices that are compatible with
1862-
/// [`ChannelManager`]. These functions serve as a convenience for building invoices with the
1862+
/// The [`lightning-invoice`] crate is useful for creating BOLT 11 invoices. However, in order to
1863+
/// construct a [`Bolt11Invoice`] that is compatible with [`ChannelManager`], use
1864+
/// [`create_bolt11_invoice`]. This method serves as a convenience for building invoices with the
18631865
/// [`PaymentHash`] and [`PaymentSecret`] returned from [`create_inbound_payment`]. To provide your
1864-
/// own [`PaymentHash`], use [`create_inbound_payment_for_hash`] or the corresponding functions in
1865-
/// the [`lightning-invoice`] `utils` module.
1866+
/// own [`PaymentHash`], override the appropriate [`Bolt11InvoiceParameters`], which is equivalent
1867+
/// to using [`create_inbound_payment_for_hash`].
18661868
///
18671869
/// [`ChannelManager`] generates an [`Event::PaymentClaimable`] once the full payment has been
18681870
/// received. Call [`claim_funds`] to release the [`PaymentPreimage`], which in turn will result in
18691871
/// an [`Event::PaymentClaimed`].
18701872
///
18711873
/// ```
18721874
/// # use lightning::events::{Event, EventsProvider, PaymentPurpose};
1873-
/// # use lightning::ln::channelmanager::AChannelManager;
1875+
/// # use lightning::ln::channelmanager::{AChannelManager, Bolt11InvoiceParameters};
18741876
/// #
18751877
/// # fn example<T: AChannelManager>(channel_manager: T) {
18761878
/// # let channel_manager = channel_manager.get_cm();
1877-
/// // Or use utils::create_invoice_from_channelmanager
1878-
/// let known_payment_hash = match channel_manager.create_inbound_payment(
1879-
/// Some(10_000_000), 3600, None
1880-
/// ) {
1881-
/// Ok((payment_hash, _payment_secret)) => {
1882-
/// println!("Creating inbound payment {}", payment_hash);
1883-
/// payment_hash
1879+
/// let params = Bolt11InvoiceParameters {
1880+
/// amount_msats: Some(10_000_000),
1881+
/// invoice_expiry_delta_secs: Some(3600),
1882+
/// ..Default::default()
1883+
/// };
1884+
/// let invoice = match channel_manager.create_bolt11_invoice(params) {
1885+
/// Ok(invoice) => {
1886+
/// println!("Creating invoice with payment hash {}", invoice.payment_hash());
1887+
/// invoice
18841888
/// },
1885-
/// Err(()) => panic!("Error creating inbound payment"),
1889+
/// Err(e) => panic!("Error creating invoice: {}", e),
18861890
/// };
18871891
///
18881892
/// // On the event processing thread
18891893
/// channel_manager.process_pending_events(&|event| {
18901894
/// match event {
18911895
/// Event::PaymentClaimable { payment_hash, purpose, .. } => match purpose {
18921896
/// PaymentPurpose::Bolt11InvoicePayment { payment_preimage: Some(payment_preimage), .. } => {
1893-
/// assert_eq!(payment_hash, known_payment_hash);
1897+
/// assert_eq!(payment_hash.0, invoice.payment_hash().as_ref());
18941898
/// println!("Claiming payment {}", payment_hash);
18951899
/// channel_manager.claim_funds(payment_preimage);
18961900
/// },
18971901
/// PaymentPurpose::Bolt11InvoicePayment { payment_preimage: None, .. } => {
18981902
/// println!("Unknown payment hash: {}", payment_hash);
18991903
/// },
19001904
/// PaymentPurpose::SpontaneousPayment(payment_preimage) => {
1901-
/// assert_ne!(payment_hash, known_payment_hash);
1905+
/// assert_ne!(payment_hash.0, invoice.payment_hash().as_ref());
19021906
/// println!("Claiming spontaneous payment {}", payment_hash);
19031907
/// channel_manager.claim_funds(payment_preimage);
19041908
/// },
19051909
/// // ...
19061910
/// # _ => {},
19071911
/// },
19081912
/// Event::PaymentClaimed { payment_hash, amount_msat, .. } => {
1909-
/// assert_eq!(payment_hash, known_payment_hash);
1913+
/// assert_eq!(payment_hash.0, invoice.payment_hash().as_ref());
19101914
/// println!("Claimed {} msats", amount_msat);
19111915
/// },
19121916
/// // ...
@@ -1917,8 +1921,8 @@ where
19171921
/// # }
19181922
/// ```
19191923
///
1920-
/// For paying an invoice, [`lightning-invoice`] provides a `payment` module with convenience
1921-
/// functions for use with [`send_payment`].
1924+
/// For paying an invoice, see the [`bolt11_payment`] module with convenience functions for use with
1925+
/// [`send_payment`].
19221926
///
19231927
/// ```
19241928
/// # use lightning::events::{Event, EventsProvider};
@@ -2252,8 +2256,10 @@ where
22522256
/// [`list_recent_payments`]: Self::list_recent_payments
22532257
/// [`abandon_payment`]: Self::abandon_payment
22542258
/// [`lightning-invoice`]: https://docs.rs/lightning_invoice/latest/lightning_invoice
2259+
/// [`create_bolt11_invoice`]: Self::create_bolt11_invoice
22552260
/// [`create_inbound_payment`]: Self::create_inbound_payment
22562261
/// [`create_inbound_payment_for_hash`]: Self::create_inbound_payment_for_hash
2262+
/// [`bolt11_payment`]: crate::ln::bolt11_payment
22572263
/// [`claim_funds`]: Self::claim_funds
22582264
/// [`send_payment`]: Self::send_payment
22592265
/// [`offers`]: crate::offers
@@ -9240,6 +9246,145 @@ where
92409246
self.finish_close_channel(failure);
92419247
}
92429248
}
9249+
9250+
/// Utility for creating a BOLT11 invoice that can be verified by [`ChannelManager`] without
9251+
/// storing any additional state. It achieves this by including a [`PaymentSecret`] in the
9252+
/// invoice which it uses to verify that the invoice has not expired and the payment amount is
9253+
/// sufficient, reproducing the [`PaymentPreimage`] if applicable.
9254+
pub fn create_bolt11_invoice(
9255+
&self, params: Bolt11InvoiceParameters,
9256+
) -> Result<Bolt11Invoice, SignOrCreationError<()>> {
9257+
let Bolt11InvoiceParameters {
9258+
amount_msats, description, invoice_expiry_delta_secs, min_final_cltv_expiry_delta,
9259+
payment_hash,
9260+
} = params;
9261+
9262+
let currency =
9263+
Network::from_chain_hash(self.chain_hash).map(Into::into).unwrap_or(Currency::Bitcoin);
9264+
9265+
#[cfg(feature = "std")]
9266+
let duration_since_epoch = {
9267+
use std::time::SystemTime;
9268+
SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)
9269+
.expect("for the foreseeable future this shouldn't happen")
9270+
};
9271+
#[cfg(not(feature = "std"))]
9272+
let duration_since_epoch =
9273+
Duration::from_secs(self.highest_seen_timestamp.load(Ordering::Acquire) as u64);
9274+
9275+
if let Some(min_final_cltv_expiry_delta) = min_final_cltv_expiry_delta {
9276+
if min_final_cltv_expiry_delta.saturating_add(3) < MIN_FINAL_CLTV_EXPIRY_DELTA {
9277+
return Err(SignOrCreationError::CreationError(CreationError::MinFinalCltvExpiryDeltaTooShort));
9278+
}
9279+
}
9280+
9281+
let (payment_hash, payment_secret) = match payment_hash {
9282+
Some(payment_hash) => {
9283+
let payment_secret = self
9284+
.create_inbound_payment_for_hash(
9285+
payment_hash, amount_msats,
9286+
invoice_expiry_delta_secs.unwrap_or(DEFAULT_EXPIRY_TIME as u32),
9287+
min_final_cltv_expiry_delta,
9288+
)
9289+
.map_err(|()| SignOrCreationError::CreationError(CreationError::InvalidAmount))?;
9290+
(payment_hash, payment_secret)
9291+
},
9292+
None => {
9293+
self
9294+
.create_inbound_payment(
9295+
amount_msats, invoice_expiry_delta_secs.unwrap_or(DEFAULT_EXPIRY_TIME as u32),
9296+
min_final_cltv_expiry_delta,
9297+
)
9298+
.map_err(|()| SignOrCreationError::CreationError(CreationError::InvalidAmount))?
9299+
},
9300+
};
9301+
9302+
log_trace!(self.logger, "Creating invoice with payment hash {}", &payment_hash);
9303+
9304+
let invoice = Bolt11InvoiceBuilder::new(currency);
9305+
let invoice = match description {
9306+
Bolt11InvoiceDescription::Direct(description) => invoice.description(description.into_inner().0),
9307+
Bolt11InvoiceDescription::Hash(hash) => invoice.description_hash(hash.0),
9308+
};
9309+
9310+
let mut invoice = invoice
9311+
.duration_since_epoch(duration_since_epoch)
9312+
.payee_pub_key(self.get_our_node_id())
9313+
.payment_hash(Hash::from_slice(&payment_hash.0).unwrap())
9314+
.payment_secret(payment_secret)
9315+
.basic_mpp()
9316+
.min_final_cltv_expiry_delta(
9317+
// Add a buffer of 3 to the delta if present, otherwise use LDK's minimum.
9318+
min_final_cltv_expiry_delta.map(|x| x.saturating_add(3)).unwrap_or(MIN_FINAL_CLTV_EXPIRY_DELTA).into()
9319+
);
9320+
9321+
if let Some(invoice_expiry_delta_secs) = invoice_expiry_delta_secs{
9322+
invoice = invoice.expiry_time(Duration::from_secs(invoice_expiry_delta_secs.into()));
9323+
}
9324+
9325+
if let Some(amount_msats) = amount_msats {
9326+
invoice = invoice.amount_milli_satoshis(amount_msats);
9327+
}
9328+
9329+
let channels = self.list_channels();
9330+
let route_hints = super::invoice_utils::sort_and_filter_channels(channels, amount_msats, &self.logger);
9331+
for hint in route_hints {
9332+
invoice = invoice.private_route(hint);
9333+
}
9334+
9335+
let raw_invoice = invoice.build_raw().map_err(|e| SignOrCreationError::CreationError(e))?;
9336+
let signature = self.node_signer.sign_invoice(&raw_invoice, Recipient::Node);
9337+
9338+
raw_invoice
9339+
.sign(|_| signature)
9340+
.map(|invoice| Bolt11Invoice::from_signed(invoice).unwrap())
9341+
.map_err(|e| SignOrCreationError::SignError(e))
9342+
}
9343+
}
9344+
9345+
/// Parameters used with [`create_bolt11_invoice`].
9346+
///
9347+
/// [`create_bolt11_invoice`]: ChannelManager::create_bolt11_invoice
9348+
pub struct Bolt11InvoiceParameters {
9349+
/// The amount for the invoice, if any.
9350+
pub amount_msats: Option<u64>,
9351+
9352+
/// The description for what the invoice is for, or hash of such description.
9353+
pub description: Bolt11InvoiceDescription,
9354+
9355+
/// The invoice expiration relative to its creation time. If not set, the invoice will expire in
9356+
/// [`DEFAULT_EXPIRY_TIME`] by default.
9357+
///
9358+
/// The creation time used is the duration since the Unix epoch for `std` builds. For non-`std`
9359+
/// builds, the highest block timestamp seen is used instead.
9360+
pub invoice_expiry_delta_secs: Option<u32>,
9361+
9362+
/// The minimum `cltv_expiry` for the last HTLC in the route. If not set, will use
9363+
/// [`MIN_FINAL_CLTV_EXPIRY_DELTA`].
9364+
///
9365+
/// If set, must be at least [`MIN_FINAL_CLTV_EXPIRY_DELTA`], and a three-block buffer will be
9366+
/// added as well to allow for up to a few new block confirmations during routing.
9367+
pub min_final_cltv_expiry_delta: Option<u16>,
9368+
9369+
/// The payment hash used in the invoice. If not set, a payment hash will be generated using a
9370+
/// preimage that can be reproduced by [`ChannelManager`] without storing any state.
9371+
///
9372+
/// Uses the payment hash if set. This may be useful if you're building an on-chain swap or
9373+
/// involving another protocol where the payment hash is also involved outside the scope of
9374+
/// lightning.
9375+
pub payment_hash: Option<PaymentHash>,
9376+
}
9377+
9378+
impl Default for Bolt11InvoiceParameters {
9379+
fn default() -> Self {
9380+
Self {
9381+
amount_msats: None,
9382+
description: Bolt11InvoiceDescription::Direct(Description::empty()),
9383+
invoice_expiry_delta_secs: None,
9384+
min_final_cltv_expiry_delta: None,
9385+
payment_hash: None,
9386+
}
9387+
}
92439388
}
92449389

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

0 commit comments

Comments
 (0)