Skip to content

Add ChannelManager::create_bolt11_invoice #3389

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Nov 11, 2024
43 changes: 34 additions & 9 deletions lightning-invoice/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,25 +233,45 @@ pub struct Bolt11Invoice {
signed_invoice: SignedRawBolt11Invoice,
}

/// Represents the description of an invoice which has to be either a directly included string or
/// a hash of a description provided out of band.
#[derive(Eq, PartialEq, Debug, Clone, Ord, PartialOrd)]
pub enum Bolt11InvoiceDescription {
/// Description of what the invoice is for
Direct(Description),

/// Hash of the description of what the invoice is for
Hash(Sha256),
}

impl Display for Bolt11InvoiceDescription {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Bolt11InvoiceDescription::Direct(desc) => write!(f, "{}", desc.0),
Bolt11InvoiceDescription::Hash(hash) => write!(f, "{}", hash.0),
}
}
}

/// Represents the description of an invoice which has to be either a directly included string or
/// a hash of a description provided out of band.
///
/// This is not exported to bindings users as we don't have a good way to map the reference lifetimes making this
/// practically impossible to use safely in languages like C.
#[derive(Eq, PartialEq, Debug, Clone, Ord, PartialOrd)]
pub enum Bolt11InvoiceDescription<'f> {
pub enum Bolt11InvoiceDescriptionRef<'f> {
/// Reference to the directly supplied description in the invoice
Direct(&'f Description),

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

impl<'f> Display for Bolt11InvoiceDescription<'f> {
impl<'f> Display for Bolt11InvoiceDescriptionRef<'f> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Bolt11InvoiceDescription::Direct(desc) => write!(f, "{}", desc.0),
Bolt11InvoiceDescription::Hash(hash) => write!(f, "{}", hash.0),
Bolt11InvoiceDescriptionRef::Direct(desc) => write!(f, "{}", desc.0),
Bolt11InvoiceDescriptionRef::Hash(hash) => write!(f, "{}", hash.0),
}
}
}
Expand Down Expand Up @@ -708,7 +728,7 @@ impl<H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBui
pub fn invoice_description(self, description: Bolt11InvoiceDescription) -> InvoiceBuilder<tb::True, H, T, C, S, M> {
match description {
Bolt11InvoiceDescription::Direct(desc) => {
self.description(desc.clone().into_inner().0)
self.description(desc.0.0)
}
Bolt11InvoiceDescription::Hash(hash) => {
self.description_hash(hash.0)
Expand Down Expand Up @@ -1374,11 +1394,11 @@ impl Bolt11Invoice {
/// Return the description or a hash of it for longer ones
///
/// This is not exported to bindings users because we don't yet export Bolt11InvoiceDescription
pub fn description(&self) -> Bolt11InvoiceDescription {
pub fn description(&self) -> Bolt11InvoiceDescriptionRef {
if let Some(direct) = self.signed_invoice.description() {
return Bolt11InvoiceDescription::Direct(direct);
return Bolt11InvoiceDescriptionRef::Direct(direct);
} else if let Some(hash) = self.signed_invoice.description_hash() {
return Bolt11InvoiceDescription::Hash(hash);
return Bolt11InvoiceDescriptionRef::Hash(hash);
}
unreachable!("ensured by constructor");
}
Expand Down Expand Up @@ -1580,6 +1600,11 @@ impl Description {
}
}

/// Creates an empty `Description`.
pub fn empty() -> Self {
Description(UntrustedString(String::new()))
}

/// Returns the underlying description [`UntrustedString`]
pub fn into_inner(self) -> UntrustedString {
self.0
Expand Down Expand Up @@ -2211,7 +2236,7 @@ mod test {
assert_eq!(invoice.private_routes(), vec![&PrivateRoute(route_1), &PrivateRoute(route_2)]);
assert_eq!(
invoice.description(),
Bolt11InvoiceDescription::Hash(&Sha256(sha256::Hash::from_slice(&[3;32][..]).unwrap()))
Bolt11InvoiceDescriptionRef::Hash(&Sha256(sha256::Hash::from_slice(&[3;32][..]).unwrap()))
);
assert_eq!(invoice.payment_hash(), &sha256::Hash::from_slice(&[21;32][..]).unwrap());
assert_eq!(invoice.payment_secret(), &PaymentSecret([42; 32]));
Expand Down
183 changes: 164 additions & 19 deletions lightning/src/ln/channelmanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ use {
crate::offers::refund::RefundMaybeWithDerivedMetadataBuilder,
};

use lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription, CreationError, Currency, Description, InvoiceBuilder as Bolt11InvoiceBuilder, SignOrCreationError, DEFAULT_EXPIRY_TIME};

use alloc::collections::{btree_map, BTreeMap};

use crate::io;
Expand Down Expand Up @@ -1730,56 +1732,58 @@ where
///
/// ## BOLT 11 Invoices
///
/// The [`lightning-invoice`] crate is useful for creating BOLT 11 invoices. Specifically, use the
/// functions in its `utils` module for constructing invoices that are compatible with
/// [`ChannelManager`]. These functions serve as a convenience for building invoices with the
/// The [`lightning-invoice`] crate is useful for creating BOLT 11 invoices. However, in order to
/// construct a [`Bolt11Invoice`] that is compatible with [`ChannelManager`], use
/// [`create_bolt11_invoice`]. This method serves as a convenience for building invoices with the
/// [`PaymentHash`] and [`PaymentSecret`] returned from [`create_inbound_payment`]. To provide your
/// own [`PaymentHash`], use [`create_inbound_payment_for_hash`] or the corresponding functions in
/// the [`lightning-invoice`] `utils` module.
/// own [`PaymentHash`], override the appropriate [`Bolt11InvoiceParameters`], which is equivalent
/// to using [`create_inbound_payment_for_hash`].
///
/// [`ChannelManager`] generates an [`Event::PaymentClaimable`] once the full payment has been
/// received. Call [`claim_funds`] to release the [`PaymentPreimage`], which in turn will result in
/// an [`Event::PaymentClaimed`].
///
/// ```
/// # use lightning::events::{Event, EventsProvider, PaymentPurpose};
/// # use lightning::ln::channelmanager::AChannelManager;
/// # use lightning::ln::channelmanager::{AChannelManager, Bolt11InvoiceParameters};
/// #
/// # fn example<T: AChannelManager>(channel_manager: T) {
/// # let channel_manager = channel_manager.get_cm();
/// // Or use utils::create_invoice_from_channelmanager
/// let known_payment_hash = match channel_manager.create_inbound_payment(
/// Some(10_000_000), 3600, None
/// ) {
/// Ok((payment_hash, _payment_secret)) => {
/// println!("Creating inbound payment {}", payment_hash);
/// payment_hash
/// let params = Bolt11InvoiceParameters {
/// amount_msats: Some(10_000_000),
/// invoice_expiry_delta_secs: Some(3600),
/// ..Default::default()
/// };
/// let invoice = match channel_manager.create_bolt11_invoice(params) {
/// Ok(invoice) => {
/// println!("Creating invoice with payment hash {}", invoice.payment_hash());
/// invoice
/// },
/// Err(()) => panic!("Error creating inbound payment"),
/// Err(e) => panic!("Error creating invoice: {}", e),
/// };
///
/// // On the event processing thread
/// channel_manager.process_pending_events(&|event| {
/// match event {
/// Event::PaymentClaimable { payment_hash, purpose, .. } => match purpose {
/// PaymentPurpose::Bolt11InvoicePayment { payment_preimage: Some(payment_preimage), .. } => {
/// assert_eq!(payment_hash, known_payment_hash);
/// assert_eq!(payment_hash.0, invoice.payment_hash().as_ref());
/// println!("Claiming payment {}", payment_hash);
/// channel_manager.claim_funds(payment_preimage);
/// },
/// PaymentPurpose::Bolt11InvoicePayment { payment_preimage: None, .. } => {
/// println!("Unknown payment hash: {}", payment_hash);
/// },
/// PaymentPurpose::SpontaneousPayment(payment_preimage) => {
/// assert_ne!(payment_hash, known_payment_hash);
/// assert_ne!(payment_hash.0, invoice.payment_hash().as_ref());
/// println!("Claiming spontaneous payment {}", payment_hash);
/// channel_manager.claim_funds(payment_preimage);
/// },
/// // ...
/// # _ => {},
/// },
/// Event::PaymentClaimed { payment_hash, amount_msat, .. } => {
/// assert_eq!(payment_hash, known_payment_hash);
/// assert_eq!(payment_hash.0, invoice.payment_hash().as_ref());
/// println!("Claimed {} msats", amount_msat);
/// },
/// // ...
Expand All @@ -1790,8 +1794,8 @@ where
/// # }
/// ```
///
/// For paying an invoice, [`lightning-invoice`] provides a `payment` module with convenience
/// functions for use with [`send_payment`].
/// For paying an invoice, see the [`bolt11_payment`] module with convenience functions for use with
/// [`send_payment`].
///
/// ```
/// # use lightning::events::{Event, EventsProvider};
Expand Down Expand Up @@ -2125,8 +2129,10 @@ where
/// [`list_recent_payments`]: Self::list_recent_payments
/// [`abandon_payment`]: Self::abandon_payment
/// [`lightning-invoice`]: https://docs.rs/lightning_invoice/latest/lightning_invoice
/// [`create_bolt11_invoice`]: Self::create_bolt11_invoice
/// [`create_inbound_payment`]: Self::create_inbound_payment
/// [`create_inbound_payment_for_hash`]: Self::create_inbound_payment_for_hash
/// [`bolt11_payment`]: crate::ln::bolt11_payment
/// [`claim_funds`]: Self::claim_funds
/// [`send_payment`]: Self::send_payment
/// [`offers`]: crate::offers
Expand Down Expand Up @@ -9093,6 +9099,145 @@ where
self.finish_close_channel(failure);
}
}

/// Utility for creating a BOLT11 invoice that can be verified by [`ChannelManager`] without
/// storing any additional state. It achieves this by including a [`PaymentSecret`] in the
/// invoice which it uses to verify that the invoice has not expired and the payment amount is
/// sufficient, reproducing the [`PaymentPreimage`] if applicable.
pub fn create_bolt11_invoice(
&self, params: Bolt11InvoiceParameters,
) -> Result<Bolt11Invoice, SignOrCreationError<()>> {
let Bolt11InvoiceParameters {
amount_msats, description, invoice_expiry_delta_secs, min_final_cltv_expiry_delta,
payment_hash,
} = params;

let currency =
Network::from_chain_hash(self.chain_hash).map(Into::into).unwrap_or(Currency::Bitcoin);

#[cfg(feature = "std")]
let duration_since_epoch = {
use std::time::SystemTime;
SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)
.expect("for the foreseeable future this shouldn't happen")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this panics when time is before 1970, not too far forwards :)

};
#[cfg(not(feature = "std"))]
let duration_since_epoch =
Duration::from_secs(self.highest_seen_timestamp.load(Ordering::Acquire) as u64);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be nice to write up a comment here on why its okay to use the timestamp "raw". It could be two hours in the future but generally only like 10-30 minutes in the past. Maybe we should handle short-ish timestamps for no-std clients but its probably fine, just would be good to write down why we think that's okay.


if let Some(min_final_cltv_expiry_delta) = min_final_cltv_expiry_delta {
if min_final_cltv_expiry_delta.saturating_add(3) < MIN_FINAL_CLTV_EXPIRY_DELTA {
return Err(SignOrCreationError::CreationError(CreationError::MinFinalCltvExpiryDeltaTooShort));
}
}

let (payment_hash, payment_secret) = match payment_hash {
Some(payment_hash) => {
let payment_secret = self
.create_inbound_payment_for_hash(
payment_hash, amount_msats,
invoice_expiry_delta_secs.unwrap_or(DEFAULT_EXPIRY_TIME as u32),
min_final_cltv_expiry_delta,
)
.map_err(|()| SignOrCreationError::CreationError(CreationError::InvalidAmount))?;
(payment_hash, payment_secret)
},
None => {
self
.create_inbound_payment(
amount_msats, invoice_expiry_delta_secs.unwrap_or(DEFAULT_EXPIRY_TIME as u32),
min_final_cltv_expiry_delta,
)
.map_err(|()| SignOrCreationError::CreationError(CreationError::InvalidAmount))?
},
};

log_trace!(self.logger, "Creating invoice with payment hash {}", &payment_hash);

let invoice = Bolt11InvoiceBuilder::new(currency);
let invoice = match description {
Bolt11InvoiceDescription::Direct(description) => invoice.description(description.into_inner().0),
Bolt11InvoiceDescription::Hash(hash) => invoice.description_hash(hash.0),
};

let mut invoice = invoice
.duration_since_epoch(duration_since_epoch)
.payee_pub_key(self.get_our_node_id())
.payment_hash(Hash::from_slice(&payment_hash.0).unwrap())
.payment_secret(payment_secret)
.basic_mpp()
.min_final_cltv_expiry_delta(
// Add a buffer of 3 to the delta if present, otherwise use LDK's minimum.
min_final_cltv_expiry_delta.map(|x| x.saturating_add(3)).unwrap_or(MIN_FINAL_CLTV_EXPIRY_DELTA).into()
);

if let Some(invoice_expiry_delta_secs) = invoice_expiry_delta_secs{
invoice = invoice.expiry_time(Duration::from_secs(invoice_expiry_delta_secs.into()));
}

if let Some(amount_msats) = amount_msats {
invoice = invoice.amount_milli_satoshis(amount_msats);
}

let channels = self.list_channels();
let route_hints = super::invoice_utils::sort_and_filter_channels(channels, amount_msats, &self.logger);
for hint in route_hints {
invoice = invoice.private_route(hint);
}

let raw_invoice = invoice.build_raw().map_err(|e| SignOrCreationError::CreationError(e))?;
let signature = self.node_signer.sign_invoice(&raw_invoice, Recipient::Node);

raw_invoice
.sign(|_| signature)
.map(|invoice| Bolt11Invoice::from_signed(invoice).unwrap())
.map_err(|e| SignOrCreationError::SignError(e))
}
}

/// Parameters used with [`create_bolt11_invoice`].
///
/// [`create_bolt11_invoice`]: ChannelManager::create_bolt11_invoice
pub struct Bolt11InvoiceParameters {
/// The amount for the invoice, if any.
pub amount_msats: Option<u64>,

/// The description for what the invoice is for, or hash of such description.
pub description: Bolt11InvoiceDescription,

/// The invoice expiration relative to its creation time. If not set, the invoice will expire in
/// [`DEFAULT_EXPIRY_TIME`] by default.
///
/// The creation time used is the duration since the Unix epoch for `std` builds. For non-`std`
/// builds, the highest block timestamp seen is used instead.
pub invoice_expiry_delta_secs: Option<u32>,

/// The minimum `cltv_expiry` for the last HTLC in the route. If not set, will use
/// [`MIN_FINAL_CLTV_EXPIRY_DELTA`].
///
/// If set, must be at least [`MIN_FINAL_CLTV_EXPIRY_DELTA`], and a three-block buffer will be
/// added as well to allow for up to a few new block confirmations during routing.
pub min_final_cltv_expiry_delta: Option<u16>,

/// The payment hash used in the invoice. If not set, a payment hash will be generated using a
/// preimage that can be reproduced by [`ChannelManager`] without storing any state.
///
/// Uses the payment hash if set. This may be useful if you're building an on-chain swap or
/// involving another protocol where the payment hash is also involved outside the scope of
/// lightning.
pub payment_hash: Option<PaymentHash>,
}

impl Default for Bolt11InvoiceParameters {
fn default() -> Self {
Self {
amount_msats: None,
description: Bolt11InvoiceDescription::Direct(Description::empty()),
invoice_expiry_delta_secs: None,
min_final_cltv_expiry_delta: None,
payment_hash: None,
}
}
}

macro_rules! create_offer_builder { ($self: ident, $builder: ty) => {
Expand Down
Loading
Loading