Skip to content

Commit 1ba97fb

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 498f233 commit 1ba97fb

File tree

4 files changed

+54
-10
lines changed

4 files changed

+54
-10
lines changed

lightning-invoice/src/payment.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,10 +145,8 @@ 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 recipient_onion = RecipientOnionFields {
149-
payment_secret: Some(*invoice.payment_secret()),
150-
payment_metadata: invoice.payment_metadata().map(|v| v.clone()),
151-
};
148+
let mut recipient_onion = RecipientOnionFields::secret_only(*invoice.payment_secret());
149+
recipient_onion.payment_metadata = invoice.payment_metadata().map(|v| v.clone());
152150
let mut payment_params = PaymentParameters::from_node_id(invoice.recover_payee_pub_key(),
153151
invoice.min_final_cltv_expiry_delta() as u32)
154152
.with_expiry_time(expiry_time_from_unix_epoch(invoice).as_secs())

lightning/src/ln/channelmanager.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3487,13 +3487,13 @@ where
34873487
let (cltv_expiry, onion_payload, payment_data, phantom_shared_secret, mut onion_fields) = match routing {
34883488
PendingHTLCRouting::Receive { payment_data, payment_metadata, incoming_cltv_expiry, phantom_shared_secret } => {
34893489
let _legacy_hop_data = Some(payment_data.clone());
3490-
let onion_fields =
3491-
RecipientOnionFields { payment_secret: Some(payment_data.payment_secret), payment_metadata };
3490+
let onion_fields = RecipientOnionFields { payment_secret: Some(payment_data.payment_secret),
3491+
payment_metadata, custom_tlvs: None };
34923492
(incoming_cltv_expiry, OnionPayload::Invoice { _legacy_hop_data },
34933493
Some(payment_data), phantom_shared_secret, onion_fields)
34943494
},
34953495
PendingHTLCRouting::ReceiveKeysend { payment_preimage, payment_metadata, incoming_cltv_expiry } => {
3496-
let onion_fields = RecipientOnionFields { payment_secret: None, payment_metadata };
3496+
let onion_fields = RecipientOnionFields { payment_secret: None, payment_metadata, custom_tlvs: None };
34973497
(incoming_cltv_expiry, OnionPayload::Spontaneous(payment_preimage),
34983498
None, None, onion_fields)
34993499
},

lightning/src/ln/outbound_payment.rs

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -431,10 +431,20 @@ 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+
/// Custom TLVs allow sending extra application-specific data with a payment. They provide
435+
/// additional flexibility on top of payment metadata, as while other implementations may
436+
/// require payment metadata to reflect metadata provided in an invoice, custom TLVs
437+
/// do not have this restriction.
438+
///
439+
/// Note that the if this field is `Some`, it will contain properly ordered TLVs, each
440+
/// represented by a `(u64, Vec<u8>)` for its type number and serialized value respectively.
441+
/// This is validated when setting this field using [`Self::with_custom_tlvs`].
442+
pub(super) custom_tlvs: Option<Vec<(u64, Vec<u8>)>>,
434443
}
435444

436445
impl_writeable_tlv_based!(RecipientOnionFields, {
437446
(0, payment_secret, option),
447+
(1, custom_tlvs, option),
438448
(2, payment_metadata, option),
439449
});
440450

@@ -443,7 +453,7 @@ impl RecipientOnionFields {
443453
/// set of onion fields for today's BOLT11 invoices - most nodes require a [`PaymentSecret`]
444454
/// but do not require or provide any further data.
445455
pub fn secret_only(payment_secret: PaymentSecret) -> Self {
446-
Self { payment_secret: Some(payment_secret), payment_metadata: None }
456+
Self { payment_secret: Some(payment_secret), payment_metadata: None, custom_tlvs: None }
447457
}
448458

449459
/// Creates a new [`RecipientOnionFields`] with no fields. This generally does not create
@@ -452,7 +462,25 @@ impl RecipientOnionFields {
452462
///
453463
/// [`ChannelManager::send_spontaneous_payment`]: super::channelmanager::ChannelManager::send_spontaneous_payment
454464
pub fn spontaneous_empty() -> Self {
455-
Self { payment_secret: None, payment_metadata: None }
465+
Self { payment_secret: None, payment_metadata: None, custom_tlvs: None }
466+
}
467+
468+
/// Creates a new [`RecipientOnionFields`], setting `custom_tlvs`. Each TLV is provided as a
469+
/// `(u64, Vec<u8>)` for the type number and serialized value respectively. TLV type numbers
470+
/// must be unique, in increasing order, and within the range reserved for custom types, i.e.
471+
/// >= 2^16, otherwise this method will return `Err(())`.
472+
pub fn with_custom_tlvs(mut self, custom_tlvs: Vec<(u64, Vec<u8>)>) -> Result<Self, ()> {
473+
let mut prev_type = None;
474+
for (typ, _) in custom_tlvs.iter() {
475+
if *typ < 1 << 16 { return Err(()); }
476+
match prev_type {
477+
Some(prev) if prev >= *typ => return Err(()),
478+
_ => {},
479+
}
480+
prev_type = Some(*typ);
481+
}
482+
self.custom_tlvs = Some(custom_tlvs);
483+
Ok(self)
456484
}
457485

458486
/// When we have received some HTLC(s) towards an MPP payment, as we receive further HTLC(s) we
@@ -765,6 +793,7 @@ impl OutboundPayments {
765793
(*total_msat, RecipientOnionFields {
766794
payment_secret: *payment_secret,
767795
payment_metadata: payment_metadata.clone(),
796+
custom_tlvs: None,
768797
}, *keysend_preimage)
769798
},
770799
PendingOutboundPayment::Legacy { .. } => {
@@ -1444,6 +1473,23 @@ mod tests {
14441473

14451474
use alloc::collections::VecDeque;
14461475

1476+
#[test]
1477+
fn test_recipient_onion_fields_with_custom_tlvs() {
1478+
let onion_fields = RecipientOnionFields::spontaneous_empty();
1479+
1480+
let bad_type_range_tlvs = vec![
1481+
(0, vec![42]),
1482+
(1, vec![42; 32]),
1483+
];
1484+
assert!(onion_fields.clone().with_custom_tlvs(bad_type_range_tlvs).is_err());
1485+
1486+
let good_tlvs = vec![
1487+
((1 << 16) + 1, vec![42]),
1488+
((1 << 16) + 3, vec![42; 32]),
1489+
];
1490+
assert!(onion_fields.with_custom_tlvs(good_tlvs).is_ok());
1491+
}
1492+
14471493
#[test]
14481494
#[cfg(feature = "std")]
14491495
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
@@ -3050,7 +3050,7 @@ fn do_test_payment_metadata_consistency(do_reload: bool, do_modify: bool) {
30503050

30513051
// Send the MPP payment, delivering the updated commitment state to nodes[1].
30523052
nodes[0].node.send_payment(payment_hash, RecipientOnionFields {
3053-
payment_secret: Some(payment_secret), payment_metadata: Some(payment_metadata),
3053+
payment_secret: Some(payment_secret), payment_metadata: Some(payment_metadata), custom_tlvs: None,
30543054
}, payment_id, route_params.clone(), Retry::Attempts(1)).unwrap();
30553055
check_added_monitors!(nodes[0], 2);
30563056

0 commit comments

Comments
 (0)