Skip to content

Commit 2e15df7

Browse files
authored
Merge pull request #2127 from TheBlueMatt/2023-03-payment-metadata
Support sending `PaymentMetadata` in HTLCs
2 parents a332bfc + ef8e377 commit 2e15df7

File tree

13 files changed

+605
-109
lines changed

13 files changed

+605
-109
lines changed

lightning-invoice/src/de.rs

+2
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,8 @@ impl FromBase32 for TaggedField {
460460
Ok(TaggedField::PrivateRoute(PrivateRoute::from_base32(field_data)?)),
461461
constants::TAG_PAYMENT_SECRET =>
462462
Ok(TaggedField::PaymentSecret(PaymentSecret::from_base32(field_data)?)),
463+
constants::TAG_PAYMENT_METADATA =>
464+
Ok(TaggedField::PaymentMetadata(Vec::<u8>::from_base32(field_data)?)),
463465
constants::TAG_FEATURES =>
464466
Ok(TaggedField::Features(InvoiceFeatures::from_base32(field_data)?)),
465467
_ => {

lightning-invoice/src/lib.rs

+94-28
Original file line numberDiff line numberDiff line change
@@ -218,10 +218,13 @@ pub const DEFAULT_MIN_FINAL_CLTV_EXPIRY_DELTA: u64 = 18;
218218
/// * `D`: exactly one [`TaggedField::Description`] or [`TaggedField::DescriptionHash`]
219219
/// * `H`: exactly one [`TaggedField::PaymentHash`]
220220
/// * `T`: the timestamp is set
221+
/// * `C`: the CLTV expiry is set
222+
/// * `S`: the payment secret is set
223+
/// * `M`: payment metadata is set
221224
///
222225
/// This is not exported to bindings users as we likely need to manually select one set of boolean type parameters.
223226
#[derive(Eq, PartialEq, Debug, Clone)]
224-
pub struct InvoiceBuilder<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> {
227+
pub struct InvoiceBuilder<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> {
225228
currency: Currency,
226229
amount: Option<u64>,
227230
si_prefix: Option<SiPrefix>,
@@ -234,6 +237,7 @@ pub struct InvoiceBuilder<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S:
234237
phantom_t: core::marker::PhantomData<T>,
235238
phantom_c: core::marker::PhantomData<C>,
236239
phantom_s: core::marker::PhantomData<S>,
240+
phantom_m: core::marker::PhantomData<M>,
237241
}
238242

239243
/// Represents a syntactically and semantically correct lightning BOLT11 invoice.
@@ -442,6 +446,7 @@ pub enum TaggedField {
442446
Fallback(Fallback),
443447
PrivateRoute(PrivateRoute),
444448
PaymentSecret(PaymentSecret),
449+
PaymentMetadata(Vec<u8>),
445450
Features(InvoiceFeatures),
446451
}
447452

@@ -506,15 +511,16 @@ pub mod constants {
506511
pub const TAG_FALLBACK: u8 = 9;
507512
pub const TAG_PRIVATE_ROUTE: u8 = 3;
508513
pub const TAG_PAYMENT_SECRET: u8 = 16;
514+
pub const TAG_PAYMENT_METADATA: u8 = 27;
509515
pub const TAG_FEATURES: u8 = 5;
510516
}
511517

512-
impl InvoiceBuilder<tb::False, tb::False, tb::False, tb::False, tb::False> {
518+
impl InvoiceBuilder<tb::False, tb::False, tb::False, tb::False, tb::False, tb::False> {
513519
/// Construct new, empty `InvoiceBuilder`. All necessary fields have to be filled first before
514520
/// `InvoiceBuilder::build(self)` becomes available.
515-
pub fn new(currrency: Currency) -> Self {
521+
pub fn new(currency: Currency) -> Self {
516522
InvoiceBuilder {
517-
currency: currrency,
523+
currency,
518524
amount: None,
519525
si_prefix: None,
520526
timestamp: None,
@@ -526,14 +532,15 @@ impl InvoiceBuilder<tb::False, tb::False, tb::False, tb::False, tb::False> {
526532
phantom_t: core::marker::PhantomData,
527533
phantom_c: core::marker::PhantomData,
528534
phantom_s: core::marker::PhantomData,
535+
phantom_m: core::marker::PhantomData,
529536
}
530537
}
531538
}
532539

533-
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, T, C, S> {
540+
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, T, C, S, M> {
534541
/// Helper function to set the completeness flags.
535-
fn set_flags<DN: tb::Bool, HN: tb::Bool, TN: tb::Bool, CN: tb::Bool, SN: tb::Bool>(self) -> InvoiceBuilder<DN, HN, TN, CN, SN> {
536-
InvoiceBuilder::<DN, HN, TN, CN, SN> {
542+
fn set_flags<DN: tb::Bool, HN: tb::Bool, TN: tb::Bool, CN: tb::Bool, SN: tb::Bool, MN: tb::Bool>(self) -> InvoiceBuilder<DN, HN, TN, CN, SN, MN> {
543+
InvoiceBuilder::<DN, HN, TN, CN, SN, MN> {
537544
currency: self.currency,
538545
amount: self.amount,
539546
si_prefix: self.si_prefix,
@@ -546,6 +553,7 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBui
546553
phantom_t: core::marker::PhantomData,
547554
phantom_c: core::marker::PhantomData,
548555
phantom_s: core::marker::PhantomData,
556+
phantom_m: core::marker::PhantomData,
549557
}
550558
}
551559

@@ -590,7 +598,7 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBui
590598
}
591599
}
592600

593-
impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, tb::True, C, S> {
601+
impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, tb::True, C, S, M> {
594602
/// Builds a [`RawInvoice`] if no [`CreationError`] occurred while construction any of the
595603
/// fields.
596604
pub fn build_raw(self) -> Result<RawInvoice, CreationError> {
@@ -624,9 +632,9 @@ impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, tb
624632
}
625633
}
626634

627-
impl<H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<tb::False, H, T, C, S> {
635+
impl<H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<tb::False, H, T, C, S, M> {
628636
/// Set the description. This function is only available if no description (hash) was set.
629-
pub fn description(mut self, description: String) -> InvoiceBuilder<tb::True, H, T, C, S> {
637+
pub fn description(mut self, description: String) -> InvoiceBuilder<tb::True, H, T, C, S, M> {
630638
match Description::new(description) {
631639
Ok(d) => self.tagged_fields.push(TaggedField::Description(d)),
632640
Err(e) => self.error = Some(e),
@@ -635,13 +643,13 @@ impl<H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<tb::Fals
635643
}
636644

637645
/// Set the description hash. This function is only available if no description (hash) was set.
638-
pub fn description_hash(mut self, description_hash: sha256::Hash) -> InvoiceBuilder<tb::True, H, T, C, S> {
646+
pub fn description_hash(mut self, description_hash: sha256::Hash) -> InvoiceBuilder<tb::True, H, T, C, S, M> {
639647
self.tagged_fields.push(TaggedField::DescriptionHash(Sha256(description_hash)));
640648
self.set_flags()
641649
}
642650

643651
/// Set the description or description hash. This function is only available if no description (hash) was set.
644-
pub fn invoice_description(self, description: InvoiceDescription) -> InvoiceBuilder<tb::True, H, T, C, S> {
652+
pub fn invoice_description(self, description: InvoiceDescription) -> InvoiceBuilder<tb::True, H, T, C, S, M> {
645653
match description {
646654
InvoiceDescription::Direct(desc) => {
647655
self.description(desc.clone().into_inner())
@@ -653,18 +661,18 @@ impl<H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<tb::Fals
653661
}
654662
}
655663

656-
impl<D: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, tb::False, T, C, S> {
664+
impl<D: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<D, tb::False, T, C, S, M> {
657665
/// Set the payment hash. This function is only available if no payment hash was set.
658-
pub fn payment_hash(mut self, hash: sha256::Hash) -> InvoiceBuilder<D, tb::True, T, C, S> {
666+
pub fn payment_hash(mut self, hash: sha256::Hash) -> InvoiceBuilder<D, tb::True, T, C, S, M> {
659667
self.tagged_fields.push(TaggedField::PaymentHash(Sha256(hash)));
660668
self.set_flags()
661669
}
662670
}
663671

664-
impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, tb::False, C, S> {
672+
impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, tb::False, C, S, M> {
665673
/// Sets the timestamp to a specific [`SystemTime`].
666674
#[cfg(feature = "std")]
667-
pub fn timestamp(mut self, time: SystemTime) -> InvoiceBuilder<D, H, tb::True, C, S> {
675+
pub fn timestamp(mut self, time: SystemTime) -> InvoiceBuilder<D, H, tb::True, C, S, M> {
668676
match PositiveTimestamp::from_system_time(time) {
669677
Ok(t) => self.timestamp = Some(t),
670678
Err(e) => self.error = Some(e),
@@ -675,7 +683,7 @@ impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, tb
675683

676684
/// Sets the timestamp to a duration since the Unix epoch, dropping the subsecond part (which
677685
/// is not representable in BOLT 11 invoices).
678-
pub fn duration_since_epoch(mut self, time: Duration) -> InvoiceBuilder<D, H, tb::True, C, S> {
686+
pub fn duration_since_epoch(mut self, time: Duration) -> InvoiceBuilder<D, H, tb::True, C, S, M> {
679687
match PositiveTimestamp::from_duration_since_epoch(time) {
680688
Ok(t) => self.timestamp = Some(t),
681689
Err(e) => self.error = Some(e),
@@ -686,34 +694,82 @@ impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, tb
686694

687695
/// Sets the timestamp to the current system time.
688696
#[cfg(feature = "std")]
689-
pub fn current_timestamp(mut self) -> InvoiceBuilder<D, H, tb::True, C, S> {
697+
pub fn current_timestamp(mut self) -> InvoiceBuilder<D, H, tb::True, C, S, M> {
690698
let now = PositiveTimestamp::from_system_time(SystemTime::now());
691699
self.timestamp = Some(now.expect("for the foreseeable future this shouldn't happen"));
692700
self.set_flags()
693701
}
694702
}
695703

696-
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, T, tb::False, S> {
704+
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, T, tb::False, S, M> {
697705
/// Sets `min_final_cltv_expiry_delta`.
698-
pub fn min_final_cltv_expiry_delta(mut self, min_final_cltv_expiry_delta: u64) -> InvoiceBuilder<D, H, T, tb::True, S> {
706+
pub fn min_final_cltv_expiry_delta(mut self, min_final_cltv_expiry_delta: u64) -> InvoiceBuilder<D, H, T, tb::True, S, M> {
699707
self.tagged_fields.push(TaggedField::MinFinalCltvExpiryDelta(MinFinalCltvExpiryDelta(min_final_cltv_expiry_delta)));
700708
self.set_flags()
701709
}
702710
}
703711

704-
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool> InvoiceBuilder<D, H, T, C, tb::False> {
712+
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, T, C, tb::False, M> {
705713
/// Sets the payment secret and relevant features.
706-
pub fn payment_secret(mut self, payment_secret: PaymentSecret) -> InvoiceBuilder<D, H, T, C, tb::True> {
707-
let mut features = InvoiceFeatures::empty();
708-
features.set_variable_length_onion_required();
709-
features.set_payment_secret_required();
714+
pub fn payment_secret(mut self, payment_secret: PaymentSecret) -> InvoiceBuilder<D, H, T, C, tb::True, M> {
715+
let mut found_features = false;
716+
for field in self.tagged_fields.iter_mut() {
717+
if let TaggedField::Features(f) = field {
718+
found_features = true;
719+
f.set_variable_length_onion_required();
720+
f.set_payment_secret_required();
721+
}
722+
}
710723
self.tagged_fields.push(TaggedField::PaymentSecret(payment_secret));
711-
self.tagged_fields.push(TaggedField::Features(features));
724+
if !found_features {
725+
let mut features = InvoiceFeatures::empty();
726+
features.set_variable_length_onion_required();
727+
features.set_payment_secret_required();
728+
self.tagged_fields.push(TaggedField::Features(features));
729+
}
712730
self.set_flags()
713731
}
714732
}
715733

716-
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool> InvoiceBuilder<D, H, T, C, tb::True> {
734+
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, T, C, S, tb::False> {
735+
/// Sets the payment metadata.
736+
///
737+
/// By default features are set to *optionally* allow the sender to include the payment metadata.
738+
/// If you wish to require that the sender include the metadata (and fail to parse the invoice if
739+
/// they don't support payment metadata fields), you need to call
740+
/// [`InvoiceBuilder::require_payment_metadata`] after this.
741+
pub fn payment_metadata(mut self, payment_metadata: Vec<u8>) -> InvoiceBuilder<D, H, T, C, S, tb::True> {
742+
self.tagged_fields.push(TaggedField::PaymentMetadata(payment_metadata));
743+
let mut found_features = false;
744+
for field in self.tagged_fields.iter_mut() {
745+
if let TaggedField::Features(f) = field {
746+
found_features = true;
747+
f.set_payment_metadata_optional();
748+
}
749+
}
750+
if !found_features {
751+
let mut features = InvoiceFeatures::empty();
752+
features.set_payment_metadata_optional();
753+
self.tagged_fields.push(TaggedField::Features(features));
754+
}
755+
self.set_flags()
756+
}
757+
}
758+
759+
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, T, C, S, tb::True> {
760+
/// Sets forwarding of payment metadata as required. A reader of the invoice which does not
761+
/// support sending payment metadata will fail to read the invoice.
762+
pub fn require_payment_metadata(mut self) -> InvoiceBuilder<D, H, T, C, S, tb::True> {
763+
for field in self.tagged_fields.iter_mut() {
764+
if let TaggedField::Features(f) = field {
765+
f.set_payment_metadata_required();
766+
}
767+
}
768+
self
769+
}
770+
}
771+
772+
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, T, C, tb::True, M> {
717773
/// Sets the `basic_mpp` feature as optional.
718774
pub fn basic_mpp(mut self) -> Self {
719775
for field in self.tagged_fields.iter_mut() {
@@ -725,7 +781,7 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool> InvoiceBuilder<D, H, T,
725781
}
726782
}
727783

728-
impl InvoiceBuilder<tb::True, tb::True, tb::True, tb::True, tb::True> {
784+
impl<M: tb::Bool> InvoiceBuilder<tb::True, tb::True, tb::True, tb::True, tb::True, M> {
729785
/// Builds and signs an invoice using the supplied `sign_function`. This function MAY NOT fail
730786
/// and MUST produce a recoverable signature valid for the given hash and if applicable also for
731787
/// the included payee public key.
@@ -977,6 +1033,10 @@ impl RawInvoice {
9771033
find_extract!(self.known_tagged_fields(), TaggedField::PaymentSecret(ref x), x)
9781034
}
9791035

1036+
pub fn payment_metadata(&self) -> Option<&Vec<u8>> {
1037+
find_extract!(self.known_tagged_fields(), TaggedField::PaymentMetadata(ref x), x)
1038+
}
1039+
9801040
pub fn features(&self) -> Option<&InvoiceFeatures> {
9811041
find_extract!(self.known_tagged_fields(), TaggedField::Features(ref x), x)
9821042
}
@@ -1248,6 +1308,11 @@ impl Invoice {
12481308
self.signed_invoice.payment_secret().expect("was checked by constructor")
12491309
}
12501310

1311+
/// Get the payment metadata blob if one was included in the invoice
1312+
pub fn payment_metadata(&self) -> Option<&Vec<u8>> {
1313+
self.signed_invoice.payment_metadata()
1314+
}
1315+
12511316
/// Get the invoice features if they were included in the invoice
12521317
pub fn features(&self) -> Option<&InvoiceFeatures> {
12531318
self.signed_invoice.features()
@@ -1396,6 +1461,7 @@ impl TaggedField {
13961461
TaggedField::Fallback(_) => constants::TAG_FALLBACK,
13971462
TaggedField::PrivateRoute(_) => constants::TAG_PRIVATE_ROUTE,
13981463
TaggedField::PaymentSecret(_) => constants::TAG_PAYMENT_SECRET,
1464+
TaggedField::PaymentMetadata(_) => constants::TAG_PAYMENT_METADATA,
13991465
TaggedField::Features(_) => constants::TAG_FEATURES,
14001466
};
14011467

lightning-invoice/src/payment.rs

+54-2
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,10 @@ fn pay_invoice_using_amount<P: Deref>(
145145
payer: P
146146
) -> Result<(), PaymentError> where P::Target: Payer {
147147
let payment_hash = PaymentHash((*invoice.payment_hash()).into_inner());
148-
let payment_secret = Some(*invoice.payment_secret());
149-
let recipient_onion = RecipientOnionFields { payment_secret };
148+
let recipient_onion = RecipientOnionFields {
149+
payment_secret: Some(*invoice.payment_secret()),
150+
payment_metadata: invoice.payment_metadata().map(|v| v.clone()),
151+
};
150152
let mut payment_params = PaymentParameters::from_node_id(invoice.recover_payee_pub_key(),
151153
invoice.min_final_cltv_expiry_delta() as u32)
152154
.with_expiry_time(expiry_time_from_unix_epoch(invoice).as_secs())
@@ -213,6 +215,8 @@ mod tests {
213215
use super::*;
214216
use crate::{InvoiceBuilder, Currency};
215217
use bitcoin_hashes::sha256::Hash as Sha256;
218+
use lightning::events::Event;
219+
use lightning::ln::msgs::ChannelMessageHandler;
216220
use lightning::ln::{PaymentPreimage, PaymentSecret};
217221
use lightning::ln::functional_test_utils::*;
218222
use secp256k1::{SecretKey, Secp256k1};
@@ -350,4 +354,52 @@ mod tests {
350354
_ => panic!()
351355
}
352356
}
357+
358+
#[test]
359+
#[cfg(feature = "std")]
360+
fn payment_metadata_end_to_end() {
361+
// Test that a payment metadata read from an invoice passed to `pay_invoice` makes it all
362+
// the way out through the `PaymentClaimable` event.
363+
let chanmon_cfgs = create_chanmon_cfgs(2);
364+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
365+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
366+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
367+
create_announced_chan_between_nodes(&nodes, 0, 1);
368+
369+
let payment_metadata = vec![42, 43, 44, 45, 46, 47, 48, 49, 42];
370+
371+
let (payment_hash, payment_secret) =
372+
nodes[1].node.create_inbound_payment(None, 7200, None).unwrap();
373+
374+
let invoice = InvoiceBuilder::new(Currency::Bitcoin)
375+
.description("test".into())
376+
.payment_hash(Sha256::from_slice(&payment_hash.0).unwrap())
377+
.payment_secret(payment_secret)
378+
.current_timestamp()
379+
.min_final_cltv_expiry_delta(144)
380+
.amount_milli_satoshis(50_000)
381+
.payment_metadata(payment_metadata.clone())
382+
.build_signed(|hash| {
383+
Secp256k1::new().sign_ecdsa_recoverable(hash,
384+
&nodes[1].keys_manager.backing.get_node_secret_key())
385+
})
386+
.unwrap();
387+
388+
pay_invoice(&invoice, Retry::Attempts(0), nodes[0].node).unwrap();
389+
check_added_monitors(&nodes[0], 1);
390+
let send_event = SendEvent::from_node(&nodes[0]);
391+
nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &send_event.msgs[0]);
392+
commitment_signed_dance!(nodes[1], nodes[0], &send_event.commitment_msg, false);
393+
394+
expect_pending_htlcs_forwardable!(nodes[1]);
395+
396+
let mut events = nodes[1].node.get_and_clear_pending_events();
397+
assert_eq!(events.len(), 1);
398+
match events.pop().unwrap() {
399+
Event::PaymentClaimable { onion_fields, .. } => {
400+
assert_eq!(Some(payment_metadata), onion_fields.unwrap().payment_metadata);
401+
},
402+
_ => panic!("Unexpected event")
403+
}
404+
}
353405
}

lightning-invoice/src/ser.rs

+3
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,9 @@ impl ToBase32 for TaggedField {
446446
TaggedField::PaymentSecret(ref payment_secret) => {
447447
write_tagged_field(writer, constants::TAG_PAYMENT_SECRET, payment_secret)
448448
},
449+
TaggedField::PaymentMetadata(ref payment_metadata) => {
450+
write_tagged_field(writer, constants::TAG_PAYMENT_METADATA, payment_metadata)
451+
},
449452
TaggedField::Features(ref features) => {
450453
write_tagged_field(writer, constants::TAG_FEATURES, features)
451454
},

0 commit comments

Comments
 (0)