Skip to content

Commit 039b1c8

Browse files
committed
Allow users to provide custom TLVs through RecipientOnionFields
Custom TLVs allow users to send extra application-specific data with a payment. These have the additional flexibility compared to `payment_metadata` that they don't have to reflect recipient generated data provided in an invoice, in which `payment_metadata` could be reused. We ensure provided type numbers are unique, increasing, and within the experimental range with the `RecipientOnionFields::with_custom_tlvs` method. This begins sender-side support for custom TLVs.
1 parent 4b24135 commit 039b1c8

File tree

4 files changed

+74
-10
lines changed

4 files changed

+74
-10
lines changed

lightning-invoice/src/payment.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -146,10 +146,8 @@ fn pay_invoice_using_amount<P: Deref>(
146146
payer: P
147147
) -> Result<(), PaymentError> where P::Target: Payer {
148148
let payment_hash = PaymentHash((*invoice.payment_hash()).into_inner());
149-
let recipient_onion = RecipientOnionFields {
150-
payment_secret: Some(*invoice.payment_secret()),
151-
payment_metadata: invoice.payment_metadata().map(|v| v.clone()),
152-
};
149+
let mut recipient_onion = RecipientOnionFields::secret_only(*invoice.payment_secret());
150+
recipient_onion.payment_metadata = invoice.payment_metadata().map(|v| v.clone());
153151
let mut payment_params = PaymentParameters::from_node_id(invoice.recover_payee_pub_key(),
154152
invoice.min_final_cltv_expiry_delta() as u32)
155153
.with_expiry_time(expiry_time_from_unix_epoch(invoice).as_secs())

lightning/src/ln/channelmanager.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3943,15 +3943,16 @@ where
39433943
let (cltv_expiry, onion_payload, payment_data, phantom_shared_secret, mut onion_fields) = match routing {
39443944
PendingHTLCRouting::Receive { payment_data, payment_metadata, incoming_cltv_expiry, phantom_shared_secret } => {
39453945
let _legacy_hop_data = Some(payment_data.clone());
3946-
let onion_fields =
3947-
RecipientOnionFields { payment_secret: Some(payment_data.payment_secret), payment_metadata };
3946+
let onion_fields = RecipientOnionFields { payment_secret: Some(payment_data.payment_secret),
3947+
payment_metadata, custom_tlvs: vec![] };
39483948
(incoming_cltv_expiry, OnionPayload::Invoice { _legacy_hop_data },
39493949
Some(payment_data), phantom_shared_secret, onion_fields)
39503950
},
39513951
PendingHTLCRouting::ReceiveKeysend { payment_data, payment_preimage, payment_metadata, incoming_cltv_expiry } => {
39523952
let onion_fields = RecipientOnionFields {
39533953
payment_secret: payment_data.as_ref().map(|data| data.payment_secret),
3954-
payment_metadata
3954+
payment_metadata,
3955+
custom_tlvs: vec![],
39553956
};
39563957
(incoming_cltv_expiry, OnionPayload::Spontaneous(payment_preimage),
39573958
payment_data, None, onion_fields)

lightning/src/ln/outbound_payment.rs

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -431,10 +431,13 @@ pub struct RecipientOnionFields {
431431
/// [`Self::payment_secret`] and while nearly all lightning senders support secrets, metadata
432432
/// may not be supported as universally.
433433
pub payment_metadata: Option<Vec<u8>>,
434+
/// See [`Self::custom_tlvs`] for more info.
435+
pub(super) custom_tlvs: Vec<(u64, Vec<u8>)>,
434436
}
435437

436438
impl_writeable_tlv_based!(RecipientOnionFields, {
437439
(0, payment_secret, option),
440+
(1, custom_tlvs, optional_vec),
438441
(2, payment_metadata, option),
439442
});
440443

@@ -443,7 +446,7 @@ impl RecipientOnionFields {
443446
/// set of onion fields for today's BOLT11 invoices - most nodes require a [`PaymentSecret`]
444447
/// but do not require or provide any further data.
445448
pub fn secret_only(payment_secret: PaymentSecret) -> Self {
446-
Self { payment_secret: Some(payment_secret), payment_metadata: None }
449+
Self { payment_secret: Some(payment_secret), payment_metadata: None, custom_tlvs: Vec::new() }
447450
}
448451

449452
/// Creates a new [`RecipientOnionFields`] with no fields. This generally does not create
@@ -455,7 +458,46 @@ impl RecipientOnionFields {
455458
/// [`ChannelManager::send_spontaneous_payment`]: super::channelmanager::ChannelManager::send_spontaneous_payment
456459
/// [`RecipientOnionFields::secret_only`]: RecipientOnionFields::secret_only
457460
pub fn spontaneous_empty() -> Self {
458-
Self { payment_secret: None, payment_metadata: None }
461+
Self { payment_secret: None, payment_metadata: None, custom_tlvs: Vec::new() }
462+
}
463+
464+
/// Creates a new [`RecipientOnionFields`] from an existing one, adding custom TLVs. Each
465+
/// TLV is provided as a `(u64, Vec<u8>)` for the type number and serialized value
466+
/// respectively. TLV type numbers must be unique and within the range
467+
/// reserved for custom types, i.e. >= 2^16, otherwise this method will return `Err(())`.
468+
///
469+
/// This method will also error for types in the experimental range which have been
470+
/// standardized within the protocol, which only includes 5482373484 (keysend) for now.
471+
///
472+
/// See [`Self::custom_tlvs`] for more info.
473+
pub fn with_custom_tlvs(mut self, mut custom_tlvs: Vec<(u64, Vec<u8>)>) -> Result<Self, ()> {
474+
custom_tlvs.sort_unstable_by_key(|(typ, _)| *typ);
475+
let mut prev_type = None;
476+
for (typ, _) in custom_tlvs.iter() {
477+
if *typ < 1 << 16 { return Err(()); }
478+
if *typ == 5482373484 { return Err(()); } // keysend
479+
match prev_type {
480+
Some(prev) if prev >= *typ => return Err(()),
481+
_ => {},
482+
}
483+
prev_type = Some(*typ);
484+
}
485+
self.custom_tlvs = custom_tlvs;
486+
Ok(self)
487+
}
488+
489+
/// Gets the custom TLVs that will be sent or have been received.
490+
///
491+
/// Custom TLVs allow sending extra application-specific data with a payment. They provide
492+
/// additional flexibility on top of payment metadata, as while other implementations may
493+
/// require `payment_metadata` to reflect metadata provided in an invoice, custom TLVs
494+
/// do not have this restriction.
495+
///
496+
/// Note that if this field is non-empty, it will contain strictly increasing TLVs, each
497+
/// represented by a `(u64, Vec<u8>)` for its type number and serialized value respectively.
498+
/// This is validated when setting this field using [`Self::with_custom_tlvs`].
499+
pub fn custom_tlvs(&self) -> &Vec<(u64, Vec<u8>)> {
500+
&self.custom_tlvs
459501
}
460502

461503
/// When we have received some HTLC(s) towards an MPP payment, as we receive further HTLC(s) we
@@ -773,6 +815,7 @@ impl OutboundPayments {
773815
(*total_msat, RecipientOnionFields {
774816
payment_secret: *payment_secret,
775817
payment_metadata: payment_metadata.clone(),
818+
custom_tlvs: Vec::new(),
776819
}, *keysend_preimage)
777820
},
778821
PendingOutboundPayment::Legacy { .. } => {
@@ -1450,6 +1493,28 @@ mod tests {
14501493

14511494
use alloc::collections::VecDeque;
14521495

1496+
#[test]
1497+
fn test_recipient_onion_fields_with_custom_tlvs() {
1498+
let onion_fields = RecipientOnionFields::spontaneous_empty();
1499+
1500+
let bad_type_range_tlvs = vec![
1501+
(0, vec![42]),
1502+
(1, vec![42; 32]),
1503+
];
1504+
assert!(onion_fields.clone().with_custom_tlvs(bad_type_range_tlvs).is_err());
1505+
1506+
let keysend_tlv = vec![
1507+
(5482373484, vec![42; 32]),
1508+
];
1509+
assert!(onion_fields.clone().with_custom_tlvs(keysend_tlv).is_err());
1510+
1511+
let good_tlvs = vec![
1512+
((1 << 16) + 1, vec![42]),
1513+
((1 << 16) + 3, vec![42; 32]),
1514+
];
1515+
assert!(onion_fields.with_custom_tlvs(good_tlvs).is_ok());
1516+
}
1517+
14531518
#[test]
14541519
#[cfg(feature = "std")]
14551520
fn fails_paying_after_expiration() {

lightning/src/ln/payment_tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3424,7 +3424,7 @@ fn do_test_payment_metadata_consistency(do_reload: bool, do_modify: bool) {
34243424

34253425
// Send the MPP payment, delivering the updated commitment state to nodes[1].
34263426
nodes[0].node.send_payment(payment_hash, RecipientOnionFields {
3427-
payment_secret: Some(payment_secret), payment_metadata: Some(payment_metadata),
3427+
payment_secret: Some(payment_secret), payment_metadata: Some(payment_metadata), custom_tlvs: vec![],
34283428
}, payment_id, route_params.clone(), Retry::Attempts(1)).unwrap();
34293429
check_added_monitors!(nodes[0], 2);
34303430

0 commit comments

Comments
 (0)