Skip to content

Commit f958924

Browse files
committed
Extend API to allow invoice creation with a description hash
1 parent 9953d2b commit f958924

File tree

7 files changed

+106
-26
lines changed

7 files changed

+106
-26
lines changed

bindings/ldk_node.udl

Lines changed: 10 additions & 4 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 Bolt11InvoiceStringDescription {
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,13 +126,13 @@ 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]Bolt11InvoiceStringDescription 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]Bolt11InvoiceStringDescription 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]Bolt11InvoiceStringDescription 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]Bolt11InvoiceStringDescription description, u32 expiry_secs, PaymentHash payment_hash);
130136
[Throws=NodeError]
131137
Bolt11Invoice receive_via_jit_channel(u64 amount_msat, [ByRef]string description, u32 expiry_secs, u64? max_lsp_fee_limit_msat);
132138
[Throws=NodeError]

src/payment/bolt11.rs

Lines changed: 77 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,13 @@ 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, Bolt11InvoiceDescription, Description };
3434

35-
use bitcoin::hashes::sha256::Hash as Sha256;
3635
use bitcoin::hashes::Hash;
36+
use bitcoin::hashes::sha256::Hash as Sha256;
3737

3838
use std::sync::{Arc, RwLock};
39+
use std::str::FromStr;
3940

4041
/// A payment handler allowing to create and pay [BOLT 11] invoices.
4142
///
@@ -403,12 +404,22 @@ impl Bolt11Payment {
403404
/// given.
404405
///
405406
/// The inbound payment will be automatically claimed upon arrival.
407+
#[cfg(not(feature = "uniffi"))]
406408
pub fn receive(
407-
&self, amount_msat: u64, description: &str, expiry_secs: u32,
409+
&self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32,
408410
) -> Result<Bolt11Invoice, Error> {
409411
self.receive_inner(Some(amount_msat), description, expiry_secs, None)
410412
}
411413

414+
#[cfg(feature = "uniffi")]
415+
pub fn receive(
416+
&self, amount_msat: u64, description: &Bolt11InvoiceStringDescription, expiry_secs: u32,
417+
) -> Result<Bolt11Invoice, Error> {
418+
let invoice_description: Bolt11InvoiceDescription = description.try_into()?;
419+
self.receive_inner(Some(amount_msat), &invoice_description, expiry_secs, None)
420+
}
421+
422+
412423
/// Returns a payable invoice that can be used to request a payment of the amount
413424
/// given for the given payment hash.
414425
///
@@ -423,22 +434,40 @@ impl Bolt11Payment {
423434
/// [`PaymentClaimable`]: crate::Event::PaymentClaimable
424435
/// [`claim_for_hash`]: Self::claim_for_hash
425436
/// [`fail_for_hash`]: Self::fail_for_hash
437+
#[cfg(not(feature = "uniffi"))]
426438
pub fn receive_for_hash(
427-
&self, amount_msat: u64, description: &str, expiry_secs: u32, payment_hash: PaymentHash,
439+
&self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32, payment_hash: PaymentHash,
428440
) -> Result<Bolt11Invoice, Error> {
429441
self.receive_inner(Some(amount_msat), description, expiry_secs, Some(payment_hash))
430442
}
443+
444+
#[cfg(feature = "uniffi")]
445+
pub fn receive_for_hash(
446+
&self, amount_msat: u64, description: &Bolt11InvoiceStringDescription, expiry_secs: u32, payment_hash: PaymentHash,
447+
) -> Result<Bolt11Invoice, Error> {
448+
let invoice_description: Bolt11InvoiceDescription = description.try_into()?;
449+
self.receive_inner(Some(amount_msat), &invoice_description, expiry_secs, Some(payment_hash))
450+
}
431451

432452
/// Returns a payable invoice that can be used to request and receive a payment for which the
433453
/// amount is to be determined by the user, also known as a "zero-amount" invoice.
434454
///
435455
/// The inbound payment will be automatically claimed upon arrival.
456+
#[cfg(not(feature = "uniffi"))]
436457
pub fn receive_variable_amount(
437-
&self, description: &str, expiry_secs: u32,
458+
&self, description: &Bolt11InvoiceDescription, expiry_secs: u32,
438459
) -> Result<Bolt11Invoice, Error> {
439460
self.receive_inner(None, description, expiry_secs, None)
440461
}
441462

463+
#[cfg(feature = "uniffi")]
464+
pub fn receive_variable_amount(
465+
&self, description: &Bolt11InvoiceStringDescription, expiry_secs: u32,
466+
) -> Result<Bolt11Invoice, Error> {
467+
let invoice_description: Bolt11InvoiceDescription = description.try_into()?;
468+
self.receive_inner(None, &invoice_description, expiry_secs, None)
469+
}
470+
442471
/// Returns a payable invoice that can be used to request a payment for the given payment hash
443472
/// and the amount to be determined by the user, also known as a "zero-amount" invoice.
444473
///
@@ -453,24 +482,29 @@ impl Bolt11Payment {
453482
/// [`PaymentClaimable`]: crate::Event::PaymentClaimable
454483
/// [`claim_for_hash`]: Self::claim_for_hash
455484
/// [`fail_for_hash`]: Self::fail_for_hash
485+
#[cfg(not(feature = "uniffi"))]
456486
pub fn receive_variable_amount_for_hash(
457-
&self, description: &str, expiry_secs: u32, payment_hash: PaymentHash,
487+
&self, description: &Bolt11InvoiceDescription, expiry_secs: u32, payment_hash: PaymentHash,
458488
) -> Result<Bolt11Invoice, Error> {
459489
self.receive_inner(None, description, expiry_secs, Some(payment_hash))
460490
}
461491

462-
fn receive_inner(
463-
&self, amount_msat: Option<u64>, description: &str, expiry_secs: u32,
464-
manual_claim_payment_hash: Option<PaymentHash>,
492+
#[cfg(feature = "uniffi")]
493+
pub fn receive_variable_amount_for_hash(
494+
&self, description: &Bolt11InvoiceStringDescription, expiry_secs: u32, payment_hash: PaymentHash,
465495
) -> Result<Bolt11Invoice, Error> {
466-
let invoice_description = Bolt11InvoiceDescription::Direct(
467-
Description::new(description.to_string()).map_err(|_| Error::InvoiceCreationFailed)?,
468-
);
496+
let invoice_description: Bolt11InvoiceDescription = description.try_into()?;
497+
self.receive_inner(None, &invoice_description, expiry_secs, Some(payment_hash))
498+
}
469499

500+
pub(crate) fn receive_inner(
501+
&self, amount_msat: Option<u64>, invoice_description: &Bolt11InvoiceDescription, expiry_secs: u32,
502+
manual_claim_payment_hash: Option<PaymentHash>,
503+
) -> Result<Bolt11Invoice, Error> {
470504
let invoice = {
471505
let invoice_params = Bolt11InvoiceParameters {
472506
amount_msats: amount_msat,
473-
description: invoice_description,
507+
description: invoice_description.clone(),
474508
invoice_expiry_delta_secs: Some(expiry_secs),
475509
payment_hash: manual_claim_payment_hash,
476510
..Default::default()
@@ -740,3 +774,33 @@ impl Bolt11Payment {
740774
Ok(())
741775
}
742776
}
777+
778+
/// Represents the description of an invoice which has to be either a directly included string or
779+
/// a hash of a description provided out of band.
780+
pub enum Bolt11InvoiceStringDescription { // use same name (no string)
781+
/// Contains a full description.
782+
Direct{
783+
/// Description of what the invoice is for
784+
description: String
785+
},
786+
/// Contains a hash.
787+
Hash{
788+
/// Hash of the description of what the invoice is for
789+
hash: String
790+
}
791+
}
792+
793+
impl TryInto<Bolt11InvoiceDescription> for &Bolt11InvoiceStringDescription {
794+
type Error = Error;
795+
796+
fn try_into(self) -> Result<Bolt11InvoiceDescription, Self::Error> {
797+
match self {
798+
Bolt11InvoiceStringDescription::Direct{description} => {
799+
Description::new(description.clone()).map(Bolt11InvoiceDescription::Direct).map_err(|_| Error::InvalidInvoice)
800+
},
801+
Bolt11InvoiceStringDescription::Hash{hash} => {
802+
Sha256::from_str(&hash).map(lightning_invoice::Sha256).map(Bolt11InvoiceDescription::Hash).map_err(|_| Error::InvalidInvoice)
803+
},
804+
}
805+
}
806+
}

src/payment/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ pub use onchain::OnchainPayment;
2020
pub use spontaneous::SpontaneousPayment;
2121
pub use store::{LSPFeeLimits, PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus};
2222
pub use unified_qr::{QrPaymentResult, UnifiedQrPayment};
23+
pub use bolt11::Bolt11InvoiceStringDescription;
2324

2425
/// Represents information used to send a payment.
2526
#[derive(Clone, Debug, PartialEq)]

src/payment/unified_qr.rs

Lines changed: 5 additions & 2 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,8 +99,11 @@ impl UnifiedQrPayment {
9999
},
100100
};
101101

102+
let invoice_description = Bolt11InvoiceDescription::Direct(
103+
Description::new(description.to_string()).map_err(|_| Error::InvoiceCreationFailed)?,
104+
);
102105
let bolt11_invoice =
103-
match self.bolt11_invoice.receive(amount_msats, description, expiry_sec) {
106+
match self.bolt11_invoice.receive_inner(Some(amount_msats), &invoice_description, expiry_sec, None) {
104107
Ok(invoice) => Some(invoice),
105108
Err(e) => {
106109
log_error!(self.logger, "Failed to create invoice {}", e);

src/uniffi_types.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ pub use bip39::Mnemonic;
3636

3737
pub use vss_client::headers::{VssHeaderProvider, VssHeaderProviderError};
3838

39+
pub use crate::payment::Bolt11InvoiceStringDescription;
40+
3941
use crate::UniffiCustomTypeConverter;
4042

4143
use crate::builder::sanitize_alias;

tests/common/mod.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use lightning::routing::gossip::NodeAlias;
2121
use lightning::util::persist::KVStore;
2222
use lightning::util::test_utils::TestStore;
2323

24+
use lightning_invoice::{Bolt11InvoiceDescription, Description};
2425
use lightning_types::payment::{PaymentHash, PaymentPreimage};
2526

2627
use lightning_persister::fs_store::FilesystemStore;
@@ -552,7 +553,8 @@ pub(crate) fn do_channel_full_cycle<E: ElectrumApi>(
552553

553554
println!("\nB receive");
554555
let invoice_amount_1_msat = 2500_000;
555-
let invoice = node_b.bolt11_payment().receive(invoice_amount_1_msat, &"asdf", 9217).unwrap();
556+
let invoice_description: Bolt11InvoiceDescription = Bolt11InvoiceDescription::Direct(Description::new(String::from("asdf")).unwrap());
557+
let invoice = node_b.bolt11_payment().receive(invoice_amount_1_msat, &invoice_description, 9217).unwrap();
556558

557559
println!("\nA send");
558560
let payment_id = node_a.bolt11_payment().send(&invoice, None).unwrap();
@@ -598,7 +600,7 @@ pub(crate) fn do_channel_full_cycle<E: ElectrumApi>(
598600

599601
// Test under-/overpayment
600602
let invoice_amount_2_msat = 2500_000;
601-
let invoice = node_b.bolt11_payment().receive(invoice_amount_2_msat, &"asdf", 9217).unwrap();
603+
let invoice = node_b.bolt11_payment().receive(invoice_amount_2_msat, &invoice_description, 9217).unwrap();
602604

603605
let underpaid_amount = invoice_amount_2_msat - 1;
604606
assert_eq!(
@@ -607,7 +609,7 @@ pub(crate) fn do_channel_full_cycle<E: ElectrumApi>(
607609
);
608610

609611
println!("\nB overpaid receive");
610-
let invoice = node_b.bolt11_payment().receive(invoice_amount_2_msat, &"asdf", 9217).unwrap();
612+
let invoice = node_b.bolt11_payment().receive(invoice_amount_2_msat, &invoice_description, 9217).unwrap();
611613
let overpaid_amount_msat = invoice_amount_2_msat + 100;
612614

613615
println!("\nA overpaid send");
@@ -637,7 +639,7 @@ pub(crate) fn do_channel_full_cycle<E: ElectrumApi>(
637639
// Test "zero-amount" invoice payment
638640
println!("\nB receive_variable_amount_payment");
639641
let variable_amount_invoice =
640-
node_b.bolt11_payment().receive_variable_amount(&"asdf", 9217).unwrap();
642+
node_b.bolt11_payment().receive_variable_amount(&invoice_description, 9217).unwrap();
641643
let determined_amount_msat = 2345_678;
642644
assert_eq!(
643645
Err(NodeError::InvalidInvoice),
@@ -676,7 +678,7 @@ pub(crate) fn do_channel_full_cycle<E: ElectrumApi>(
676678
let manual_payment_hash = PaymentHash(Sha256::hash(&manual_preimage.0).to_byte_array());
677679
let manual_invoice = node_b
678680
.bolt11_payment()
679-
.receive_for_hash(invoice_amount_3_msat, &"asdf", 9217, manual_payment_hash)
681+
.receive_for_hash(invoice_amount_3_msat, &invoice_description, 9217, manual_payment_hash)
680682
.unwrap();
681683
let manual_payment_id = node_a.bolt11_payment().send(&manual_invoice, None).unwrap();
682684

@@ -714,7 +716,7 @@ pub(crate) fn do_channel_full_cycle<E: ElectrumApi>(
714716
PaymentHash(Sha256::hash(&manual_fail_preimage.0).to_byte_array());
715717
let manual_fail_invoice = node_b
716718
.bolt11_payment()
717-
.receive_for_hash(invoice_amount_3_msat, &"asdf", 9217, manual_fail_payment_hash)
719+
.receive_for_hash(invoice_amount_3_msat, &invoice_description, 9217, manual_fail_payment_hash)
718720
.unwrap();
719721
let manual_fail_payment_id = node_a.bolt11_payment().send(&manual_fail_invoice, None).unwrap();
720722

tests/integration_tests_rust.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ use lightning::util::persist::KVStore;
2424
use bitcoincore_rpc::RpcApi;
2525

2626
use bitcoin::Amount;
27+
use lightning_invoice::{Bolt11InvoiceDescription, Description};
2728

2829
use std::sync::Arc;
2930

@@ -189,7 +190,8 @@ fn multi_hop_sending() {
189190
max_channel_saturation_power_of_half: Some(2),
190191
};
191192

192-
let invoice = nodes[4].bolt11_payment().receive(2_500_000, &"asdf", 9217).unwrap();
193+
let invoice_description = Bolt11InvoiceDescription::Direct(Description::new(String::from("asdf")).unwrap());
194+
let invoice = nodes[4].bolt11_payment().receive(2_500_000, &invoice_description, 9217).unwrap();
193195
nodes[0].bolt11_payment().send(&invoice, Some(sending_params)).unwrap();
194196

195197
expect_event!(nodes[1], PaymentForwarded);

0 commit comments

Comments
 (0)