Skip to content

Commit 52a6607

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. Because a lightning implementation may fail receiving a payment due to not being able to deserialize an onion payload's TLV stream, we also only allow a user to add custom TLVs if they provide a valid TLV stream using `RecipientOnionFields::with_custom_tlvs`. This begins sender-side support for custom TLVs.
1 parent 1f1ae97 commit 52a6607

File tree

5 files changed

+72
-10
lines changed

5 files changed

+72
-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
@@ -13,10 +13,12 @@ use bitcoin::hashes::Hash;
1313
use bitcoin::hashes::sha256::Hash as Sha256;
1414
use bitcoin::secp256k1::{self, Secp256k1, SecretKey};
1515

16+
use crate::offers::TlvStream;
1617
use crate::sign::{EntropySource, NodeSigner, Recipient};
1718
use crate::events::{self, PaymentFailureReason};
1819
use crate::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
1920
use crate::ln::channelmanager::{ChannelDetails, EventCompletionAction, HTLCSource, IDEMPOTENCY_TIMEOUT_TICKS, PaymentId};
21+
use crate::ln::msgs::DecodeError;
2022
use crate::ln::onion_utils::HTLCFailReason;
2123
use crate::routing::router::{InFlightHtlcs, Path, PaymentParameters, Route, RouteParameters, Router};
2224
use crate::util::errors::APIError;
@@ -431,10 +433,20 @@ pub struct RecipientOnionFields {
431433
/// [`Self::payment_secret`] and while nearly all lightning senders support secrets, metadata
432434
/// may not be supported as universally.
433435
pub payment_metadata: Option<Vec<u8>>,
436+
/// Custom TLVs allow sending extra application-specific data with a payment. They provide
437+
/// additional flexibility on top of payment metadata, as while other implementations may
438+
/// require payment metadata to reflect metadata provided in an invoice, custom TLVs
439+
/// do not have this restriction.
440+
///
441+
/// Note that the if this field is `Some`, it will contain a properly serialized TLV stream,
442+
/// otherwise we will fail to serialize and send the payment. This can be easily done using
443+
/// [`encode_tlv_stream`], and is validated with [`Self::with_custom_tlvs`].
444+
pub(super) custom_tlvs: Option<Vec<u8>>,
434445
}
435446

436447
impl_writeable_tlv_based!(RecipientOnionFields, {
437448
(0, payment_secret, option),
449+
(1, custom_tlvs, option),
438450
(2, payment_metadata, option),
439451
});
440452

@@ -443,7 +455,7 @@ impl RecipientOnionFields {
443455
/// set of onion fields for today's BOLT11 invoices - most nodes require a [`PaymentSecret`]
444456
/// but do not require or provide any further data.
445457
pub fn secret_only(payment_secret: PaymentSecret) -> Self {
446-
Self { payment_secret: Some(payment_secret), payment_metadata: None }
458+
Self { payment_secret: Some(payment_secret), payment_metadata: None, custom_tlvs: None }
447459
}
448460

449461
/// Creates a new [`RecipientOnionFields`] with no fields. This generally does not create
@@ -452,7 +464,20 @@ impl RecipientOnionFields {
452464
///
453465
/// [`ChannelManager::send_spontaneous_payment`]: super::channelmanager::ChannelManager::send_spontaneous_payment
454466
pub fn spontaneous_empty() -> Self {
455-
Self { payment_secret: None, payment_metadata: None }
467+
Self { payment_secret: None, payment_metadata: None, custom_tlvs: None }
468+
}
469+
470+
/// Creates a new [`RecipientOnionFields`], settting `custom_tlvs` if the provided bytes can
471+
/// deserialized properly into TLVs, otherwise returns the error faced while decoding. This
472+
/// will also reject TLVs with type numbers outside the range reserved for custom types, i.e.
473+
/// <2^16.
474+
pub fn with_custom_tlvs(mut self, custom_tlvs: Vec<u8>) -> Result<Self, DecodeError> {
475+
let tlv_stream = TlvStream::try_new(&custom_tlvs)?;
476+
for tlv in tlv_stream.into_iter() {
477+
if tlv.get_type() < 1 << 16 { return Err(DecodeError::InvalidValue); }
478+
}
479+
self.custom_tlvs = Some(custom_tlvs);
480+
Ok(self)
456481
}
457482

458483
/// When we have received some HTLC(s) towards an MPP payment, as we receive further HTLC(s) we
@@ -765,6 +790,7 @@ impl OutboundPayments {
765790
(*total_msat, RecipientOnionFields {
766791
payment_secret: *payment_secret,
767792
payment_metadata: payment_metadata.clone(),
793+
custom_tlvs: None,
768794
}, *keysend_preimage)
769795
},
770796
PendingOutboundPayment::Legacy { .. } => {
@@ -1444,6 +1470,26 @@ mod tests {
14441470

14451471
use alloc::collections::VecDeque;
14461472

1473+
#[test]
1474+
fn test_recipient_onion_fields_with_custom_tlvs() {
1475+
let onion_fields = RecipientOnionFields::spontaneous_empty();
1476+
1477+
let bad_tlvs = vec![42; 32];
1478+
assert!(onion_fields.clone().with_custom_tlvs(bad_tlvs).is_err());
1479+
1480+
let bad_type_range_tlvs = _get_encoded_tlv_stream!({
1481+
(0, 42, required),
1482+
(1, Some(vec![42; 32]), option),
1483+
});
1484+
assert!(onion_fields.clone().with_custom_tlvs(bad_type_range_tlvs).is_err());
1485+
1486+
let good_tlvs = _get_encoded_tlv_stream!({
1487+
((1 << 16) + 1, 42, required),
1488+
((1 << 16) + 3, Some(vec![42; 32]), option),
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

lightning/src/offers/merkle.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use bitcoin::hashes::{Hash, HashEngine, sha256};
1313
use bitcoin::secp256k1::{Message, PublicKey, Secp256k1, self};
1414
use bitcoin::secp256k1::schnorr::Signature;
1515
use crate::io;
16+
use crate::ln::msgs::DecodeError;
1617
use crate::util::ser::{BigSize, Readable, Writeable, Writer};
1718

1819
use crate::prelude::*;
@@ -155,6 +156,23 @@ impl<'a> TlvStream<'a> {
155156
}
156157
}
157158

159+
/// Checks if `data` is a valid TLV stream before returning a new [`TlvStream`].
160+
pub fn try_new(data: &'a [u8]) -> Result<Self, DecodeError> {
161+
let mut cursor = io::Cursor::new(data);
162+
let len = cursor.get_ref().len() as u64;
163+
while cursor.position() < len {
164+
<BigSize as Readable>::read(&mut cursor)?;
165+
let length = <BigSize as Readable>::read(&mut cursor)?.0;
166+
let offset = cursor.position();
167+
cursor.set_position(offset + length);
168+
}
169+
if cursor.position() > len {
170+
Err(DecodeError::ShortRead)
171+
} else {
172+
Ok(Self::new(data))
173+
}
174+
}
175+
158176
pub fn range<T>(self, types: T) -> impl core::iter::Iterator<Item = TlvRecord<'a>>
159177
where
160178
T: core::ops::RangeBounds<u64> + Clone,

0 commit comments

Comments
 (0)