Skip to content
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