diff --git a/lightning-invoice/src/lib.rs b/lightning-invoice/src/lib.rs index 8baedbe26cd..6f7ec225337 100644 --- a/lightning-invoice/src/lib.rs +++ b/lightning-invoice/src/lib.rs @@ -233,13 +233,33 @@ 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), @@ -247,11 +267,11 @@ pub enum Bolt11InvoiceDescription<'f> { 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), } } } @@ -708,7 +728,7 @@ impl InvoiceBui pub fn invoice_description(self, description: Bolt11InvoiceDescription) -> InvoiceBuilder { 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) @@ -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"); } @@ -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 @@ -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])); diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 3914384ca82..f2c092286ea 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -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; @@ -1730,12 +1732,12 @@ 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 @@ -1743,19 +1745,21 @@ where /// /// ``` /// # use lightning::events::{Event, EventsProvider, PaymentPurpose}; -/// # use lightning::ln::channelmanager::AChannelManager; +/// # use lightning::ln::channelmanager::{AChannelManager, Bolt11InvoiceParameters}; /// # /// # fn example(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 @@ -1763,7 +1767,7 @@ where /// 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); /// }, @@ -1771,7 +1775,7 @@ where /// 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); /// }, @@ -1779,7 +1783,7 @@ where /// # _ => {}, /// }, /// 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); /// }, /// // ... @@ -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}; @@ -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 @@ -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> { + 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") + }; + #[cfg(not(feature = "std"))] + let duration_since_epoch = + Duration::from_secs(self.highest_seen_timestamp.load(Ordering::Acquire) as u64); + + 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, + + /// 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, + + /// 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, + + /// 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, +} + +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) => { diff --git a/lightning/src/ln/invoice_utils.rs b/lightning/src/ln/invoice_utils.rs index 611e3c0531d..07e108dab06 100644 --- a/lightning/src/ln/invoice_utils.rs +++ b/lightning/src/ln/invoice_utils.rs @@ -9,10 +9,9 @@ use bitcoin::hashes::Hash; use crate::chain; use crate::chain::chaininterface::{BroadcasterInterface, FeeEstimator}; use crate::sign::{Recipient, NodeSigner, SignerProvider, EntropySource}; -use crate::types::payment::{PaymentHash, PaymentSecret}; +use crate::types::payment::PaymentHash; use crate::ln::channel_state::ChannelDetails; -use crate::ln::channelmanager::{ChannelManager, MIN_FINAL_CLTV_EXPIRY_DELTA}; -use crate::ln::channelmanager::{PhantomRouteHints, MIN_CLTV_EXPIRY_DELTA}; +use crate::ln::channelmanager::{Bolt11InvoiceParameters, ChannelManager, PhantomRouteHints, MIN_CLTV_EXPIRY_DELTA, MIN_FINAL_CLTV_EXPIRY_DELTA}; use crate::ln::inbound_payment::{create, create_from_hash, ExpandedKey}; use crate::routing::gossip::RoutingFees; use crate::routing::router::{RouteHint, RouteHintHop, Router}; @@ -75,7 +74,7 @@ where L::Target: Logger, { let description = Description::new(description).map_err(SignOrCreationError::CreationError)?; - let description = Bolt11InvoiceDescription::Direct(&description,); + let description = Bolt11InvoiceDescription::Direct(description); _create_phantom_invoice::( amt_msat, payment_hash, description, invoice_expiry_delta_secs, phantom_route_hints, entropy_source, node_signer, logger, network, min_final_cltv_expiry_delta, duration_since_epoch, @@ -130,7 +129,7 @@ where L::Target: Logger, { _create_phantom_invoice::( - amt_msat, payment_hash, Bolt11InvoiceDescription::Hash(&description_hash), + amt_msat, payment_hash, Bolt11InvoiceDescription::Hash(description_hash), invoice_expiry_delta_secs, phantom_route_hints, entropy_source, node_signer, logger, network, min_final_cltv_expiry_delta, duration_since_epoch, ) @@ -161,7 +160,7 @@ where let invoice = match description { Bolt11InvoiceDescription::Direct(description) => { - InvoiceBuilder::new(network).description(description.as_inner().0.clone()) + InvoiceBuilder::new(network).description(description.into_inner().0) } Bolt11InvoiceDescription::Hash(hash) => InvoiceBuilder::new(network).description_hash(hash.0), }; @@ -314,7 +313,7 @@ fn rotate_through_iterators>(mut vecs: Vec) -> impl }) } -#[cfg(feature = "std")] +#[deprecated(note = "Use ChannelManager::create_bolt11_invoice instead.")] /// Utility to construct an invoice. Generally, unless you want to do something like a custom /// cltv_expiry, this is what you should be using to create an invoice. The reason being, this /// method stores the invoice's payment secret and preimage in `ChannelManager`, so (a) the user @@ -331,9 +330,8 @@ fn rotate_through_iterators>(mut vecs: Vec) -> impl /// /// [`MIN_FINAL_CLTV_EXPIRY_DETLA`]: crate::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA pub fn create_invoice_from_channelmanager( - channelmanager: &ChannelManager, node_signer: NS, logger: L, - network: Currency, amt_msat: Option, description: String, invoice_expiry_delta_secs: u32, - min_final_cltv_expiry_delta: Option, + channelmanager: &ChannelManager, amt_msat: Option, + description: String, invoice_expiry_delta_secs: u32, min_final_cltv_expiry_delta: Option, ) -> Result> where M::Target: chain::Watch<::EcdsaSigner>, @@ -346,16 +344,18 @@ where MR::Target: MessageRouter, L::Target: Logger, { - use std::time::SystemTime; - let duration = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) - .expect("for the foreseeable future this shouldn't happen"); - create_invoice_from_channelmanager_and_duration_since_epoch( - channelmanager, node_signer, logger, network, amt_msat, - description, duration, invoice_expiry_delta_secs, min_final_cltv_expiry_delta, - ) + let description = Description::new(description).map_err(SignOrCreationError::CreationError)?; + let params = Bolt11InvoiceParameters { + amount_msats: amt_msat, + description: Bolt11InvoiceDescription::Direct(description), + invoice_expiry_delta_secs: Some(invoice_expiry_delta_secs), + min_final_cltv_expiry_delta, + payment_hash: None, + }; + channelmanager.create_bolt11_invoice(params) } -#[cfg(feature = "std")] +#[deprecated(note = "Use ChannelManager::create_bolt11_invoice instead.")] /// Utility to construct an invoice. Generally, unless you want to do something like a custom /// cltv_expiry, this is what you should be using to create an invoice. The reason being, this /// method stores the invoice's payment secret and preimage in `ChannelManager`, so (a) the user @@ -373,97 +373,9 @@ where /// /// [`MIN_FINAL_CLTV_EXPIRY_DETLA`]: crate::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA pub fn create_invoice_from_channelmanager_with_description_hash( - channelmanager: &ChannelManager, node_signer: NS, logger: L, - network: Currency, amt_msat: Option, description_hash: Sha256, - invoice_expiry_delta_secs: u32, min_final_cltv_expiry_delta: Option, -) -> Result> -where - M::Target: chain::Watch<::EcdsaSigner>, - T::Target: BroadcasterInterface, - ES::Target: EntropySource, - NS::Target: NodeSigner, - SP::Target: SignerProvider, - F::Target: FeeEstimator, - R::Target: Router, - MR::Target: MessageRouter, - L::Target: Logger, -{ - use std::time::SystemTime; - - let duration = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .expect("for the foreseeable future this shouldn't happen"); - - create_invoice_from_channelmanager_with_description_hash_and_duration_since_epoch( - channelmanager, node_signer, logger, network, amt_msat, - description_hash, duration, invoice_expiry_delta_secs, min_final_cltv_expiry_delta, - ) -} - -/// Utility to construct an invoice. Generally, unless you want to do something like a custom -/// `cltv_expiry`, this is what you should be using to create an invoice. -#[cfg_attr(feature = "std", doc = "")] -#[cfg_attr(feature = "std", doc = "See [`create_invoice_from_channelmanager_with_description_hash`] for more information.")] -#[cfg_attr(feature = "std", doc = "")] -#[cfg_attr(feature = "std", doc = "This can be used in a `no_std` environment, where [`std::time::SystemTime`] is not available and the current time is supplied by the caller.")] -pub fn create_invoice_from_channelmanager_with_description_hash_and_duration_since_epoch( - channelmanager: &ChannelManager, node_signer: NS, logger: L, - network: Currency, amt_msat: Option, description_hash: Sha256, - duration_since_epoch: Duration, invoice_expiry_delta_secs: u32, min_final_cltv_expiry_delta: Option, -) -> Result> -where - M::Target: chain::Watch<::EcdsaSigner>, - T::Target: BroadcasterInterface, - ES::Target: EntropySource, - NS::Target: NodeSigner, - SP::Target: SignerProvider, - F::Target: FeeEstimator, - R::Target: Router, - MR::Target: MessageRouter, - L::Target: Logger, -{ - _create_invoice_from_channelmanager_and_duration_since_epoch( - channelmanager, node_signer, logger, network, amt_msat, - Bolt11InvoiceDescription::Hash(&description_hash), - duration_since_epoch, invoice_expiry_delta_secs, min_final_cltv_expiry_delta, - ) -} - -/// Utility to construct an invoice. Generally, unless you want to do something like a custom -/// `cltv_expiry`, this is what you should be using to create an invoice. -#[cfg_attr(feature = "std", doc = "")] -#[cfg_attr(feature = "std", doc = "See [`create_invoice_from_channelmanager`] for more information.")] -#[cfg_attr(feature = "std", doc = "")] -#[cfg_attr(feature = "std", doc = "This version can be used in a `no_std` environment, where [`std::time::SystemTime`] is not available and the current time is supplied by the caller.")] -pub fn create_invoice_from_channelmanager_and_duration_since_epoch( - channelmanager: &ChannelManager, node_signer: NS, logger: L, - network: Currency, amt_msat: Option, description: String, duration_since_epoch: Duration, - invoice_expiry_delta_secs: u32, min_final_cltv_expiry_delta: Option, -) -> Result> -where - M::Target: chain::Watch<::EcdsaSigner>, - T::Target: BroadcasterInterface, - ES::Target: EntropySource, - NS::Target: NodeSigner, - SP::Target: SignerProvider, - F::Target: FeeEstimator, - R::Target: Router, - MR::Target: MessageRouter, - L::Target: Logger, -{ - _create_invoice_from_channelmanager_and_duration_since_epoch( - channelmanager, node_signer, logger, network, amt_msat, - Bolt11InvoiceDescription::Direct( - &Description::new(description).map_err(SignOrCreationError::CreationError)?, - ), - duration_since_epoch, invoice_expiry_delta_secs, min_final_cltv_expiry_delta, - ) -} - -fn _create_invoice_from_channelmanager_and_duration_since_epoch( - channelmanager: &ChannelManager, node_signer: NS, logger: L, - network: Currency, amt_msat: Option, description: Bolt11InvoiceDescription, - duration_since_epoch: Duration, invoice_expiry_delta_secs: u32, min_final_cltv_expiry_delta: Option, + channelmanager: &ChannelManager, amt_msat: Option, + description_hash: Sha256, invoice_expiry_delta_secs: u32, + min_final_cltv_expiry_delta: Option, ) -> Result> where M::Target: chain::Watch<::EcdsaSigner>, @@ -476,30 +388,28 @@ where MR::Target: MessageRouter, L::Target: Logger, { - if min_final_cltv_expiry_delta.is_some() && min_final_cltv_expiry_delta.unwrap().saturating_add(3) < MIN_FINAL_CLTV_EXPIRY_DELTA { - return Err(SignOrCreationError::CreationError(CreationError::MinFinalCltvExpiryDeltaTooShort)); - } - - // `create_inbound_payment` only returns an error if the amount is greater than the total bitcoin - // supply. - let (payment_hash, payment_secret) = channelmanager - .create_inbound_payment(amt_msat, invoice_expiry_delta_secs, min_final_cltv_expiry_delta) - .map_err(|()| SignOrCreationError::CreationError(CreationError::InvalidAmount))?; - _create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash( - channelmanager, node_signer, logger, network, amt_msat, description, duration_since_epoch, - invoice_expiry_delta_secs, payment_hash, payment_secret, min_final_cltv_expiry_delta) + let params = Bolt11InvoiceParameters { + amount_msats: amt_msat, + description: Bolt11InvoiceDescription::Hash(description_hash), + invoice_expiry_delta_secs: Some(invoice_expiry_delta_secs), + min_final_cltv_expiry_delta, + payment_hash: None, + }; + channelmanager.create_bolt11_invoice(params) } -/// See [`create_invoice_from_channelmanager_and_duration_since_epoch`] +#[deprecated(note = "Use ChannelManager::create_bolt11_invoice instead.")] +/// See [`create_invoice_from_channelmanager`]. +/// /// This version allows for providing custom [`PaymentHash`] and description hash for the invoice. /// /// 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 and want to set the /// description hash. -pub fn create_invoice_from_channelmanager_with_description_hash_and_duration_since_epoch_with_payment_hash( - channelmanager: &ChannelManager, node_signer: NS, logger: L, - network: Currency, amt_msat: Option, description_hash: Sha256, duration_since_epoch: Duration, - invoice_expiry_delta_secs: u32, payment_hash: PaymentHash, min_final_cltv_expiry_delta: Option, +pub fn create_invoice_from_channelmanager_with_description_hash_and_payment_hash( + channelmanager: &ChannelManager, amt_msat: Option, + description_hash: Sha256, invoice_expiry_delta_secs: u32, payment_hash: PaymentHash, + min_final_cltv_expiry_delta: Option, ) -> Result> where M::Target: chain::Watch<::EcdsaSigner>, @@ -512,26 +422,26 @@ where MR::Target: MessageRouter, L::Target: Logger, { - let payment_secret = channelmanager - .create_inbound_payment_for_hash(payment_hash, amt_msat, invoice_expiry_delta_secs, - min_final_cltv_expiry_delta) - .map_err(|()| SignOrCreationError::CreationError(CreationError::InvalidAmount))?; - _create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash( - channelmanager, node_signer, logger, network, amt_msat, - Bolt11InvoiceDescription::Hash(&description_hash), - duration_since_epoch, invoice_expiry_delta_secs, payment_hash, payment_secret, + let params = Bolt11InvoiceParameters { + amount_msats: amt_msat, + description: Bolt11InvoiceDescription::Hash(description_hash), + invoice_expiry_delta_secs: Some(invoice_expiry_delta_secs), min_final_cltv_expiry_delta, - ) + payment_hash: Some(payment_hash), + }; + channelmanager.create_bolt11_invoice(params) } -/// See [`create_invoice_from_channelmanager_and_duration_since_epoch`] +#[deprecated(note = "Use ChannelManager::create_bolt11_invoice instead.")] +/// See [`create_invoice_from_channelmanager`]. +/// /// This version allows for providing a custom [`PaymentHash`] for the invoice. /// 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 fn create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash( - channelmanager: &ChannelManager, node_signer: NS, logger: L, - network: Currency, amt_msat: Option, description: String, duration_since_epoch: Duration, - invoice_expiry_delta_secs: u32, payment_hash: PaymentHash, min_final_cltv_expiry_delta: Option, +pub fn create_invoice_from_channelmanager_with_payment_hash( + channelmanager: &ChannelManager, amt_msat: Option, + description: String, invoice_expiry_delta_secs: u32, payment_hash: PaymentHash, + min_final_cltv_expiry_delta: Option, ) -> Result> where M::Target: chain::Watch<::EcdsaSigner>, @@ -544,82 +454,15 @@ where MR::Target: MessageRouter, L::Target: Logger, { - let payment_secret = channelmanager - .create_inbound_payment_for_hash(payment_hash, amt_msat, invoice_expiry_delta_secs, - min_final_cltv_expiry_delta) - .map_err(|()| SignOrCreationError::CreationError(CreationError::InvalidAmount))?; - _create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash( - channelmanager, node_signer, logger, network, amt_msat, - Bolt11InvoiceDescription::Direct( - &Description::new(description).map_err(SignOrCreationError::CreationError)?, - ), - duration_since_epoch, invoice_expiry_delta_secs, payment_hash, payment_secret, + let description = Description::new(description).map_err(SignOrCreationError::CreationError)?; + let params = Bolt11InvoiceParameters { + amount_msats: amt_msat, + description: Bolt11InvoiceDescription::Direct(description), + invoice_expiry_delta_secs: Some(invoice_expiry_delta_secs), min_final_cltv_expiry_delta, - ) -} - -fn _create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash( - channelmanager: &ChannelManager, node_signer: NS, logger: L, - network: Currency, amt_msat: Option, description: Bolt11InvoiceDescription, - duration_since_epoch: Duration, invoice_expiry_delta_secs: u32, payment_hash: PaymentHash, - payment_secret: PaymentSecret, min_final_cltv_expiry_delta: Option, -) -> Result> -where - M::Target: chain::Watch<::EcdsaSigner>, - T::Target: BroadcasterInterface, - ES::Target: EntropySource, - NS::Target: NodeSigner, - SP::Target: SignerProvider, - F::Target: FeeEstimator, - R::Target: Router, - MR::Target: MessageRouter, - L::Target: Logger, -{ - let our_node_pubkey = channelmanager.get_our_node_id(); - let channels = channelmanager.list_channels(); - - if min_final_cltv_expiry_delta.is_some() && min_final_cltv_expiry_delta.unwrap().saturating_add(3) < MIN_FINAL_CLTV_EXPIRY_DELTA { - return Err(SignOrCreationError::CreationError(CreationError::MinFinalCltvExpiryDeltaTooShort)); - } - - log_trace!(logger, "Creating invoice with payment hash {}", &payment_hash); - - let invoice = match description { - Bolt11InvoiceDescription::Direct(description) => { - InvoiceBuilder::new(network).description(description.as_inner().0.clone()) - } - Bolt11InvoiceDescription::Hash(hash) => InvoiceBuilder::new(network).description_hash(hash.0), + payment_hash: Some(payment_hash), }; - - let mut invoice = invoice - .duration_since_epoch(duration_since_epoch) - .payee_pub_key(our_node_pubkey) - .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()) - .expiry_time(Duration::from_secs(invoice_expiry_delta_secs.into())); - if let Some(amt) = amt_msat { - invoice = invoice.amount_milli_satoshis(amt); - } - - let route_hints = sort_and_filter_channels(channels, amt_msat, &logger); - for hint in route_hints { - invoice = invoice.private_route(hint); - } - - let raw_invoice = match invoice.build_raw() { - Ok(inv) => inv, - Err(e) => return Err(SignOrCreationError::CreationError(e)) - }; - let signature = node_signer.sign_invoice(&raw_invoice, Recipient::Node); - let signed_raw_invoice = raw_invoice.sign(|_| signature); - match signed_raw_invoice { - Ok(inv) => Ok(Bolt11Invoice::from_signed(inv).unwrap()), - Err(e) => Err(SignOrCreationError::SignError(e)) - } + channelmanager.create_bolt11_invoice(params) } /// Sorts and filters the `channels` for an invoice, and returns the corresponding `RouteHint`s to include @@ -641,7 +484,7 @@ where /// * Limited to a total of 3 channels. /// * Sorted by lowest inbound capacity if an online channel with the minimum amount requested exists, /// otherwise sort by highest inbound capacity to give the payment the best chance of succeeding. -fn sort_and_filter_channels( +pub(super) fn sort_and_filter_channels( channels: Vec, min_inbound_capacity_msat: Option, logger: &L, @@ -864,13 +707,14 @@ impl<'a, 'b, L: Deref> WithChannelDetails<'a, 'b, L> where L::Target: Logger { mod test { use super::*; use core::time::Duration; - use lightning_invoice::{Currency, Description, Bolt11InvoiceDescription, SignOrCreationError, CreationError}; + use lightning_invoice::{Currency, Description, Bolt11InvoiceDescriptionRef, SignOrCreationError, CreationError}; use bitcoin::hashes::{Hash, sha256}; use bitcoin::hashes::sha256::Hash as Sha256; + use bitcoin::network::Network; use crate::sign::PhantomKeysManager; use crate::events::{MessageSendEvent, MessageSendEventsProvider}; use crate::types::payment::{PaymentHash, PaymentPreimage}; - use crate::ln::channelmanager::{PhantomRouteHints, MIN_FINAL_CLTV_EXPIRY_DELTA, PaymentId, RecipientOnionFields, Retry}; + use crate::ln::channelmanager::{Bolt11InvoiceParameters, PhantomRouteHints, MIN_FINAL_CLTV_EXPIRY_DELTA, PaymentId, RecipientOnionFields, Retry}; use crate::ln::functional_test_utils::*; use crate::ln::msgs::ChannelMessageHandler; use crate::routing::router::{PaymentParameters, RouteParameters}; @@ -913,15 +757,22 @@ mod test { let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); let nodes = create_network(2, &node_cfgs, &node_chanmgrs); create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 100000, 10001); + + let description = Bolt11InvoiceDescription::Direct( + Description::new("test".to_string()).unwrap() + ); let non_default_invoice_expiry_secs = 4200; - let invoice = create_invoice_from_channelmanager_and_duration_since_epoch( - nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Currency::BitcoinTestnet, - Some(10_000), "test".to_string(), Duration::from_secs(1234567), - non_default_invoice_expiry_secs, None).unwrap(); + let invoice_params = Bolt11InvoiceParameters { + amount_msats: Some(10_000), + description, + invoice_expiry_delta_secs: Some(non_default_invoice_expiry_secs), + ..Default::default() + }; + let invoice = nodes[1].node.create_bolt11_invoice(invoice_params).unwrap(); assert_eq!(invoice.amount_milli_satoshis(), Some(10_000)); // If no `min_final_cltv_expiry_delta` is specified, then it should be `MIN_FINAL_CLTV_EXPIRY_DELTA`. assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64); - assert_eq!(invoice.description(), Bolt11InvoiceDescription::Direct(&Description::new("test".to_string()).unwrap())); + assert_eq!(invoice.description(), Bolt11InvoiceDescriptionRef::Direct(&Description::new("test".to_string()).unwrap())); assert_eq!(invoice.expiry_time(), Duration::from_secs(non_default_invoice_expiry_secs.into())); // Invoice SCIDs should always use inbound SCID aliases over the real channel ID, if one is @@ -965,11 +816,17 @@ mod test { let nodes = create_network(2, &node_cfgs, &node_chanmgrs); let custom_min_final_cltv_expiry_delta = Some(50); - let invoice = create_invoice_from_channelmanager_and_duration_since_epoch( - nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Currency::BitcoinTestnet, - Some(10_000), "".into(), Duration::from_secs(1234567), 3600, - if with_custom_delta { custom_min_final_cltv_expiry_delta } else { None }, - ).unwrap(); + let description = Bolt11InvoiceDescription::Direct(Description::empty()); + let min_final_cltv_expiry_delta = + if with_custom_delta { custom_min_final_cltv_expiry_delta } else { None }; + let invoice_params = Bolt11InvoiceParameters { + amount_msats: Some(10_000), + description, + invoice_expiry_delta_secs: Some(3600), + min_final_cltv_expiry_delta, + ..Default::default() + }; + let invoice = nodes[1].node.create_bolt11_invoice(invoice_params).unwrap(); assert_eq!(invoice.min_final_cltv_expiry_delta(), if with_custom_delta { custom_min_final_cltv_expiry_delta.unwrap() + 3 /* Buffer */} else { MIN_FINAL_CLTV_EXPIRY_DELTA } as u64); } @@ -986,13 +843,17 @@ mod test { let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); let nodes = create_network(2, &node_cfgs, &node_chanmgrs); - let custom_min_final_cltv_expiry_delta = Some(21); - let invoice = create_invoice_from_channelmanager_and_duration_since_epoch( - nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Currency::BitcoinTestnet, - Some(10_000), "".into(), Duration::from_secs(1234567), 3600, - custom_min_final_cltv_expiry_delta, - ).unwrap(); + let custom_min_final_cltv_expiry_delta = Some(21); + let description = Bolt11InvoiceDescription::Direct(Description::empty()); + let invoice_params = Bolt11InvoiceParameters { + amount_msats: Some(10_000), + description, + invoice_expiry_delta_secs: Some(3600), + min_final_cltv_expiry_delta: custom_min_final_cltv_expiry_delta, + ..Default::default() + }; + let invoice = nodes[1].node.create_bolt11_invoice(invoice_params).unwrap(); assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64); } @@ -1002,14 +863,20 @@ mod test { let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); let nodes = create_network(2, &node_cfgs, &node_chanmgrs); - let description_hash = Sha256(Hash::hash("Testing description_hash".as_bytes())); - let invoice = create_invoice_from_channelmanager_with_description_hash_and_duration_since_epoch( - nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Currency::BitcoinTestnet, - Some(10_000), description_hash, Duration::from_secs(1234567), 3600, None, - ).unwrap(); + + let description = Bolt11InvoiceDescription::Hash( + Sha256(Hash::hash("Testing description_hash".as_bytes())) + ); + let invoice_params = Bolt11InvoiceParameters { + amount_msats: Some(10_000), + description, + invoice_expiry_delta_secs: Some(3600), + ..Default::default() + }; + let invoice = nodes[1].node.create_bolt11_invoice(invoice_params).unwrap(); assert_eq!(invoice.amount_milli_satoshis(), Some(10_000)); assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64); - assert_eq!(invoice.description(), Bolt11InvoiceDescription::Hash(&Sha256(Sha256::hash("Testing description_hash".as_bytes())))); + assert_eq!(invoice.description(), Bolt11InvoiceDescriptionRef::Hash(&Sha256(Sha256::hash("Testing description_hash".as_bytes())))); } #[test] @@ -1018,18 +885,55 @@ mod test { let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + let payment_hash = PaymentHash([0; 32]); - let invoice = create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash( - nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Currency::BitcoinTestnet, - Some(10_000), "test".to_string(), Duration::from_secs(1234567), 3600, - payment_hash, None, - ).unwrap(); + let description = Bolt11InvoiceDescription::Direct( + Description::new("test".to_string()).unwrap() + ); + let invoice_params = Bolt11InvoiceParameters { + amount_msats: Some(10_000), + description, + invoice_expiry_delta_secs: Some(3600), + payment_hash: Some(payment_hash), + ..Default::default() + }; + let invoice = nodes[1].node.create_bolt11_invoice(invoice_params).unwrap(); assert_eq!(invoice.amount_milli_satoshis(), Some(10_000)); assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64); - assert_eq!(invoice.description(), Bolt11InvoiceDescription::Direct(&Description::new("test".to_string()).unwrap())); + assert_eq!(invoice.description(), Bolt11InvoiceDescriptionRef::Direct(&Description::new("test".to_string()).unwrap())); assert_eq!(invoice.payment_hash(), &sha256::Hash::from_slice(&payment_hash.0[..]).unwrap()); } + #[cfg(not(feature = "std"))] + #[test] + fn creates_invoice_using_highest_seen_timestamp() { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let invoice_params = Bolt11InvoiceParameters::default(); + let invoice = nodes[1].node.create_bolt11_invoice(invoice_params).unwrap(); + let best_block = bitcoin::constants::genesis_block(Network::Testnet); + assert_eq!( + invoice.duration_since_epoch(), + Duration::from_secs(best_block.header.time.into()), + ); + } + + #[test] + fn creates_invoice_using_currency_inferred_from_chain_hash() { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let invoice_params = Bolt11InvoiceParameters::default(); + let invoice = nodes[1].node.create_bolt11_invoice(invoice_params).unwrap(); + assert_eq!(invoice.currency(), Currency::BitcoinTestnet); + assert_eq!(invoice.network(), Network::Testnet); + } + #[test] fn test_hints_has_only_public_confd_channels() { let chanmon_cfgs = create_chanmon_cfgs(2); @@ -1311,10 +1215,16 @@ mod test { invoice_node: &Node<'a, 'b, 'c>, mut chan_ids_to_match: HashSet ) { - let invoice = create_invoice_from_channelmanager_and_duration_since_epoch( - invoice_node.node, invoice_node.keys_manager, invoice_node.logger, - Currency::BitcoinTestnet, invoice_amt, "test".to_string(), Duration::from_secs(1234567), - 3600, None).unwrap(); + let description = Bolt11InvoiceDescription::Direct( + Description::new("test".to_string()).unwrap() + ); + let invoice_params = Bolt11InvoiceParameters { + amount_msats: invoice_amt, + description, + invoice_expiry_delta_secs: Some(3600), + ..Default::default() + }; + let invoice = invoice_node.node.create_bolt11_invoice(invoice_params).unwrap(); let hints = invoice.private_routes(); for hint in hints { @@ -1379,7 +1289,7 @@ mod test { }; assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64); - assert_eq!(invoice.description(), Bolt11InvoiceDescription::Direct(&Description::new("test".to_string()).unwrap())); + assert_eq!(invoice.description(), Bolt11InvoiceDescriptionRef::Direct(&Description::new("test".to_string()).unwrap())); assert_eq!(invoice.route_hints().len(), 2); assert_eq!(invoice.expiry_time(), Duration::from_secs(non_default_invoice_expiry_secs.into())); assert!(!invoice.features().unwrap().supports_basic_mpp()); @@ -1498,7 +1408,7 @@ mod test { assert_eq!(invoice.amount_milli_satoshis(), Some(20_000)); assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64); assert_eq!(invoice.expiry_time(), Duration::from_secs(non_default_invoice_expiry_secs.into())); - assert_eq!(invoice.description(), Bolt11InvoiceDescription::Hash(&Sha256(Sha256::hash("Description hash phantom invoice".as_bytes())))); + assert_eq!(invoice.description(), Bolt11InvoiceDescriptionRef::Hash(&Sha256(Sha256::hash("Description hash phantom invoice".as_bytes())))); } #[test] @@ -1949,11 +1859,18 @@ mod test { let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); let nodes = create_network(2, &node_cfgs, &node_chanmgrs); - let result = create_invoice_from_channelmanager_and_duration_since_epoch( - nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Currency::BitcoinTestnet, - Some(10_000), "Some description".into(), Duration::from_secs(1234567), 3600, Some(MIN_FINAL_CLTV_EXPIRY_DELTA - 4), + + let description = Bolt11InvoiceDescription::Direct( + Description::new("Some description".to_string()).unwrap() ); - match result { + let invoice_params = Bolt11InvoiceParameters { + amount_msats: Some(10_000), + description, + invoice_expiry_delta_secs: Some(3600), + min_final_cltv_expiry_delta: Some(MIN_FINAL_CLTV_EXPIRY_DELTA - 4), + ..Default::default() + }; + match nodes[1].node.create_bolt11_invoice(invoice_params) { Err(SignOrCreationError::CreationError(CreationError::MinFinalCltvExpiryDeltaTooShort)) => {}, _ => panic!(), }