Skip to content

Commit 92ee62f

Browse files
authored
Merge pull request #438 from joostjager/invoice-description-hash
Extend API to allow invoice creation with a description hash
2 parents b388ee1 + 3cfb9e5 commit 92ee62f

File tree

10 files changed

+167
-50
lines changed

10 files changed

+167
-50
lines changed

bindings/kotlin/ldk-node-jvm/lib/src/test/kotlin/org/lightningdevkit/ldknode/LibraryTest.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,8 @@ class LibraryTest {
222222
else -> return
223223
}
224224

225-
val invoice = node2.bolt11Payment().receive(2500000u, "asdf", 9217u)
225+
val description = Bolt11InvoiceDescription.Direct("asdf")
226+
val invoice = node2.bolt11Payment().receive(2500000u, description, 9217u)
226227

227228
node1.bolt11Payment().send(invoice, null)
228229

bindings/ldk_node.udl

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,12 @@ interface Node {
106106
boolean verify_signature([ByRef]sequence<u8> msg, [ByRef]string sig, [ByRef]PublicKey pkey);
107107
};
108108

109+
[Enum]
110+
interface Bolt11InvoiceDescription {
111+
Hash(string hash);
112+
Direct(string description);
113+
};
114+
109115
interface Bolt11Payment {
110116
[Throws=NodeError]
111117
PaymentId send([ByRef]Bolt11Invoice invoice, SendingParameters? sending_parameters);
@@ -120,17 +126,17 @@ interface Bolt11Payment {
120126
[Throws=NodeError]
121127
void fail_for_hash(PaymentHash payment_hash);
122128
[Throws=NodeError]
123-
Bolt11Invoice receive(u64 amount_msat, [ByRef]string description, u32 expiry_secs);
129+
Bolt11Invoice receive(u64 amount_msat, [ByRef]Bolt11InvoiceDescription description, u32 expiry_secs);
124130
[Throws=NodeError]
125-
Bolt11Invoice receive_for_hash(u64 amount_msat, [ByRef]string description, u32 expiry_secs, PaymentHash payment_hash);
131+
Bolt11Invoice receive_for_hash(u64 amount_msat, [ByRef]Bolt11InvoiceDescription description, u32 expiry_secs, PaymentHash payment_hash);
126132
[Throws=NodeError]
127-
Bolt11Invoice receive_variable_amount([ByRef]string description, u32 expiry_secs);
133+
Bolt11Invoice receive_variable_amount([ByRef]Bolt11InvoiceDescription description, u32 expiry_secs);
128134
[Throws=NodeError]
129-
Bolt11Invoice receive_variable_amount_for_hash([ByRef]string description, u32 expiry_secs, PaymentHash payment_hash);
135+
Bolt11Invoice receive_variable_amount_for_hash([ByRef]Bolt11InvoiceDescription description, u32 expiry_secs, PaymentHash payment_hash);
130136
[Throws=NodeError]
131-
Bolt11Invoice receive_via_jit_channel(u64 amount_msat, [ByRef]string description, u32 expiry_secs, u64? max_lsp_fee_limit_msat);
137+
Bolt11Invoice receive_via_jit_channel(u64 amount_msat, [ByRef]Bolt11InvoiceDescription description, u32 expiry_secs, u64? max_lsp_fee_limit_msat);
132138
[Throws=NodeError]
133-
Bolt11Invoice receive_variable_amount_via_jit_channel([ByRef]string description, u32 expiry_secs, u64? max_proportional_lsp_fee_limit_ppm_msat);
139+
Bolt11Invoice receive_variable_amount_via_jit_channel([ByRef]Bolt11InvoiceDescription description, u32 expiry_secs, u64? max_proportional_lsp_fee_limit_ppm_msat);
134140
};
135141

136142
interface Bolt12Payment {

bindings/python/src/ldk_node/test_ldk_node.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,8 @@ def test_channel_full_cycle(self):
185185
print("EVENT:", channel_ready_event_2)
186186
node_2.event_handled()
187187

188-
invoice = node_2.bolt11_payment().receive(2500000, "asdf", 9217)
188+
description = Bolt11InvoiceDescription.DIRECT("asdf")
189+
invoice = node_2.bolt11_payment().receive(2500000, description, 9217)
189190
node_1.bolt11_payment().send(invoice, None)
190191

191192
payment_successful_event_1 = node_1.wait_next_event()

src/liquidity.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use crate::{Config, Error};
1212
use lightning::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA;
1313
use lightning::ln::msgs::SocketAddress;
1414
use lightning::routing::router::{RouteHint, RouteHintHop};
15-
use lightning_invoice::{Bolt11Invoice, InvoiceBuilder, RoutingFees};
15+
use lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription, InvoiceBuilder, RoutingFees};
1616
use lightning_liquidity::events::Event;
1717
use lightning_liquidity::lsps0::ser::RequestId;
1818
use lightning_liquidity::lsps2::event::LSPS2ClientEvent;
@@ -196,7 +196,7 @@ where
196196
}
197197

198198
pub(crate) async fn lsps2_receive_to_jit_channel(
199-
&self, amount_msat: u64, description: &str, expiry_secs: u32,
199+
&self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32,
200200
max_total_lsp_fee_limit_msat: Option<u64>,
201201
) -> Result<(Bolt11Invoice, u64), Error> {
202202
let fee_response = self.lsps2_request_opening_fee_params().await?;
@@ -256,7 +256,7 @@ where
256256
}
257257

258258
pub(crate) async fn lsps2_receive_variable_amount_to_jit_channel(
259-
&self, description: &str, expiry_secs: u32,
259+
&self, description: &Bolt11InvoiceDescription, expiry_secs: u32,
260260
max_proportional_lsp_fee_limit_ppm_msat: Option<u64>,
261261
) -> Result<(Bolt11Invoice, u64), Error> {
262262
let fee_response = self.lsps2_request_opening_fee_params().await?;
@@ -373,8 +373,8 @@ where
373373
}
374374

375375
fn lsps2_create_jit_invoice(
376-
&self, buy_response: LSPS2BuyResponse, amount_msat: Option<u64>, description: &str,
377-
expiry_secs: u32,
376+
&self, buy_response: LSPS2BuyResponse, amount_msat: Option<u64>,
377+
description: &Bolt11InvoiceDescription, expiry_secs: u32,
378378
) -> Result<Bolt11Invoice, Error> {
379379
let lsps2_service = self.lsps2_service.as_ref().ok_or(Error::LiquiditySourceUnavailable)?;
380380

@@ -404,7 +404,7 @@ where
404404

405405
let currency = self.config.network.into();
406406
let mut invoice_builder = InvoiceBuilder::new(currency)
407-
.description(description.to_string())
407+
.invoice_description(description.clone())
408408
.payment_hash(payment_hash)
409409
.payment_secret(payment_secret)
410410
.current_timestamp()

src/payment/bolt11.rs

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,32 @@ use lightning::routing::router::{PaymentParameters, RouteParameters};
3030

3131
use lightning_types::payment::{PaymentHash, PaymentPreimage};
3232

33-
use lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription, Description};
33+
use lightning_invoice::Bolt11Invoice;
34+
use lightning_invoice::Bolt11InvoiceDescription as LdkBolt11InvoiceDescription;
3435

3536
use bitcoin::hashes::sha256::Hash as Sha256;
3637
use bitcoin::hashes::Hash;
3738

3839
use std::sync::{Arc, RwLock};
3940

41+
#[cfg(not(feature = "uniffi"))]
42+
type Bolt11InvoiceDescription = LdkBolt11InvoiceDescription;
43+
#[cfg(feature = "uniffi")]
44+
type Bolt11InvoiceDescription = crate::uniffi_types::Bolt11InvoiceDescription;
45+
46+
macro_rules! maybe_convert_description {
47+
($description: expr) => {{
48+
#[cfg(not(feature = "uniffi"))]
49+
{
50+
$description
51+
}
52+
#[cfg(feature = "uniffi")]
53+
{
54+
&LdkBolt11InvoiceDescription::try_from($description)?
55+
}
56+
}};
57+
}
58+
4059
/// A payment handler allowing to create and pay [BOLT 11] invoices.
4160
///
4261
/// Should be retrieved by calling [`Node::bolt11_payment`].
@@ -404,8 +423,9 @@ impl Bolt11Payment {
404423
///
405424
/// The inbound payment will be automatically claimed upon arrival.
406425
pub fn receive(
407-
&self, amount_msat: u64, description: &str, expiry_secs: u32,
426+
&self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32,
408427
) -> Result<Bolt11Invoice, Error> {
428+
let description = maybe_convert_description!(description);
409429
self.receive_inner(Some(amount_msat), description, expiry_secs, None)
410430
}
411431

@@ -424,8 +444,10 @@ impl Bolt11Payment {
424444
/// [`claim_for_hash`]: Self::claim_for_hash
425445
/// [`fail_for_hash`]: Self::fail_for_hash
426446
pub fn receive_for_hash(
427-
&self, amount_msat: u64, description: &str, expiry_secs: u32, payment_hash: PaymentHash,
447+
&self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32,
448+
payment_hash: PaymentHash,
428449
) -> Result<Bolt11Invoice, Error> {
450+
let description = maybe_convert_description!(description);
429451
self.receive_inner(Some(amount_msat), description, expiry_secs, Some(payment_hash))
430452
}
431453

@@ -434,8 +456,9 @@ impl Bolt11Payment {
434456
///
435457
/// The inbound payment will be automatically claimed upon arrival.
436458
pub fn receive_variable_amount(
437-
&self, description: &str, expiry_secs: u32,
459+
&self, description: &Bolt11InvoiceDescription, expiry_secs: u32,
438460
) -> Result<Bolt11Invoice, Error> {
461+
let description = maybe_convert_description!(description);
439462
self.receive_inner(None, description, expiry_secs, None)
440463
}
441464

@@ -454,23 +477,20 @@ impl Bolt11Payment {
454477
/// [`claim_for_hash`]: Self::claim_for_hash
455478
/// [`fail_for_hash`]: Self::fail_for_hash
456479
pub fn receive_variable_amount_for_hash(
457-
&self, description: &str, expiry_secs: u32, payment_hash: PaymentHash,
480+
&self, description: &Bolt11InvoiceDescription, expiry_secs: u32, payment_hash: PaymentHash,
458481
) -> Result<Bolt11Invoice, Error> {
482+
let description = maybe_convert_description!(description);
459483
self.receive_inner(None, description, expiry_secs, Some(payment_hash))
460484
}
461485

462-
fn receive_inner(
463-
&self, amount_msat: Option<u64>, description: &str, expiry_secs: u32,
464-
manual_claim_payment_hash: Option<PaymentHash>,
486+
pub(crate) fn receive_inner(
487+
&self, amount_msat: Option<u64>, invoice_description: &LdkBolt11InvoiceDescription,
488+
expiry_secs: u32, manual_claim_payment_hash: Option<PaymentHash>,
465489
) -> Result<Bolt11Invoice, Error> {
466-
let invoice_description = Bolt11InvoiceDescription::Direct(
467-
Description::new(description.to_string()).map_err(|_| Error::InvoiceCreationFailed)?,
468-
);
469-
470490
let invoice = {
471491
let invoice_params = Bolt11InvoiceParameters {
472492
amount_msats: amount_msat,
473-
description: invoice_description,
493+
description: invoice_description.clone(),
474494
invoice_expiry_delta_secs: Some(expiry_secs),
475495
payment_hash: manual_claim_payment_hash,
476496
..Default::default()
@@ -531,9 +551,10 @@ impl Bolt11Payment {
531551
///
532552
/// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md
533553
pub fn receive_via_jit_channel(
534-
&self, amount_msat: u64, description: &str, expiry_secs: u32,
554+
&self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32,
535555
max_total_lsp_fee_limit_msat: Option<u64>,
536556
) -> Result<Bolt11Invoice, Error> {
557+
let description = maybe_convert_description!(description);
537558
self.receive_via_jit_channel_inner(
538559
Some(amount_msat),
539560
description,
@@ -555,9 +576,10 @@ impl Bolt11Payment {
555576
///
556577
/// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md
557578
pub fn receive_variable_amount_via_jit_channel(
558-
&self, description: &str, expiry_secs: u32,
579+
&self, description: &Bolt11InvoiceDescription, expiry_secs: u32,
559580
max_proportional_lsp_fee_limit_ppm_msat: Option<u64>,
560581
) -> Result<Bolt11Invoice, Error> {
582+
let description = maybe_convert_description!(description);
561583
self.receive_via_jit_channel_inner(
562584
None,
563585
description,
@@ -568,8 +590,8 @@ impl Bolt11Payment {
568590
}
569591

570592
fn receive_via_jit_channel_inner(
571-
&self, amount_msat: Option<u64>, description: &str, expiry_secs: u32,
572-
max_total_lsp_fee_limit_msat: Option<u64>,
593+
&self, amount_msat: Option<u64>, description: &LdkBolt11InvoiceDescription,
594+
expiry_secs: u32, max_total_lsp_fee_limit_msat: Option<u64>,
573595
max_proportional_lsp_fee_limit_ppm_msat: Option<u64>,
574596
) -> Result<Bolt11Invoice, Error> {
575597
let liquidity_source =

src/payment/unified_qr.rs

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use crate::Config;
1818

1919
use lightning::ln::channelmanager::PaymentId;
2020
use lightning::offers::offer::Offer;
21-
use lightning_invoice::Bolt11Invoice;
21+
use lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription, Description};
2222

2323
use bip21::de::ParamKind;
2424
use bip21::{DeserializationError, DeserializeParams, Param, SerializeParams};
@@ -99,14 +99,21 @@ impl UnifiedQrPayment {
9999
},
100100
};
101101

102-
let bolt11_invoice =
103-
match self.bolt11_invoice.receive(amount_msats, description, expiry_sec) {
104-
Ok(invoice) => Some(invoice),
105-
Err(e) => {
106-
log_error!(self.logger, "Failed to create invoice {}", e);
107-
return Err(Error::InvoiceCreationFailed);
108-
},
109-
};
102+
let invoice_description = Bolt11InvoiceDescription::Direct(
103+
Description::new(description.to_string()).map_err(|_| Error::InvoiceCreationFailed)?,
104+
);
105+
let bolt11_invoice = match self.bolt11_invoice.receive_inner(
106+
Some(amount_msats),
107+
&invoice_description,
108+
expiry_sec,
109+
None,
110+
) {
111+
Ok(invoice) => Some(invoice),
112+
Err(e) => {
113+
log_error!(self.logger, "Failed to create invoice {}", e);
114+
return Err(Error::InvoiceCreationFailed);
115+
},
116+
};
110117

111118
let extras = Extras { bolt11_invoice, bolt12_offer };
112119

src/uniffi_types.rs

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ pub use lightning::util::string::UntrustedString;
2828

2929
pub use lightning_types::payment::{PaymentHash, PaymentPreimage, PaymentSecret};
3030

31-
pub use lightning_invoice::Bolt11Invoice;
31+
pub use lightning_invoice::{Bolt11Invoice, Description};
3232

3333
pub use bitcoin::{Address, BlockHash, Network, OutPoint, Txid};
3434

@@ -345,3 +345,49 @@ impl UniffiCustomTypeConverter for NodeAlias {
345345
obj.to_string()
346346
}
347347
}
348+
349+
/// Represents the description of an invoice which has to be either a directly included string or
350+
/// a hash of a description provided out of band.
351+
pub enum Bolt11InvoiceDescription {
352+
/// Contains a full description.
353+
Direct {
354+
/// Description of what the invoice is for
355+
description: String,
356+
},
357+
/// Contains a hash.
358+
Hash {
359+
/// Hash of the description of what the invoice is for
360+
hash: String,
361+
},
362+
}
363+
364+
impl TryFrom<&Bolt11InvoiceDescription> for lightning_invoice::Bolt11InvoiceDescription {
365+
type Error = Error;
366+
367+
fn try_from(value: &Bolt11InvoiceDescription) -> Result<Self, Self::Error> {
368+
match value {
369+
Bolt11InvoiceDescription::Direct { description } => {
370+
Description::new(description.clone())
371+
.map(lightning_invoice::Bolt11InvoiceDescription::Direct)
372+
.map_err(|_| Error::InvoiceCreationFailed)
373+
},
374+
Bolt11InvoiceDescription::Hash { hash } => Sha256::from_str(&hash)
375+
.map(lightning_invoice::Sha256)
376+
.map(lightning_invoice::Bolt11InvoiceDescription::Hash)
377+
.map_err(|_| Error::InvoiceCreationFailed),
378+
}
379+
}
380+
}
381+
382+
impl From<lightning_invoice::Bolt11InvoiceDescription> for Bolt11InvoiceDescription {
383+
fn from(value: lightning_invoice::Bolt11InvoiceDescription) -> Self {
384+
match value {
385+
lightning_invoice::Bolt11InvoiceDescription::Direct(description) => {
386+
Bolt11InvoiceDescription::Direct { description: description.to_string() }
387+
},
388+
lightning_invoice::Bolt11InvoiceDescription::Hash(hash) => {
389+
Bolt11InvoiceDescription::Hash { hash: hex_utils::to_string(hash.0.as_ref()) }
390+
},
391+
}
392+
}
393+
}

0 commit comments

Comments
 (0)