Skip to content
43 changes: 34 additions & 9 deletions lightning-invoice/src/lib.rs
Original file line numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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
Copy Markdown
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
Copy Markdown
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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Add `ChannelManager::create_bolt11_invoice` by jkczyz · Pull Request #3389 · lightningdevkit/rust-lightning · GitHub
Skip to content
43 changes: 34 additions & 9 deletions lightning-invoice/src/lib.rs
Original file line numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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
Copy Markdown
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
Copy Markdown
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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Add `ChannelManager::create_bolt11_invoice` by jkczyz · Pull Request #3389 · lightningdevkit/rust-lightning · GitHub
Skip to content
43 changes: 34 additions & 9 deletions lightning-invoice/src/lib.rs
Original file line numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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
Copy Markdown
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
Copy Markdown
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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Add `ChannelManager::create_bolt11_invoice` by jkczyz · Pull Request #3389 · lightningdevkit/rust-lightning · GitHub
Skip to content
43 changes: 34 additions & 9 deletions lightning-invoice/src/lib.rs
Original file line numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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
Copy Markdown
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
Copy Markdown
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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Add `ChannelManager::create_bolt11_invoice` by jkczyz · Pull Request #3389 · lightningdevkit/rust-lightning · GitHub
Skip to content
43 changes: 34 additions & 9 deletions lightning-invoice/src/lib.rs
Original file line numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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
Copy Markdown
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
Copy Markdown
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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Add `ChannelManager::create_bolt11_invoice` by jkczyz · Pull Request #3389 · lightningdevkit/rust-lightning · GitHub
Skip to content
43 changes: 34 additions & 9 deletions lightning-invoice/src/lib.rs
Original file line numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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
Copy Markdown
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
Copy Markdown
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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Add `ChannelManager::create_bolt11_invoice` by jkczyz · Pull Request #3389 · lightningdevkit/rust-lightning · GitHub
Skip to content
43 changes: 34 additions & 9 deletions lightning-invoice/src/lib.rs
Original file line numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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
Copy Markdown
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
Copy Markdown
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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Add `ChannelManager::create_bolt11_invoice` by jkczyz · Pull Request #3389 · lightningdevkit/rust-lightning · GitHub
Skip to content
43 changes: 34 additions & 9 deletions lightning-invoice/src/lib.rs
Original file line numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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
Copy Markdown
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
Copy Markdown
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