Skip to content

Commit d37bb5f

Browse files
committed
Add Custom TLVs for payment::ReceiveTlvs
- Building on the previous commit, this update allows users to include their own custom TLVs within the reply path of a sent onion message. - With this, users can attach custom data to the message, which will be returned in the response, providing more flexibility for custom use cases.
1 parent 2170c9c commit d37bb5f

File tree

10 files changed

+44
-12
lines changed

10 files changed

+44
-12
lines changed

fuzz/src/invoice_request_deser.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ fn build_response<T: secp256k1::Signing + secp256k1::Verification>(
103103
htlc_minimum_msat: 1,
104104
},
105105
payment_context,
106+
custom_data: Vec::new(),
106107
};
107108
let payee_tlvs = payee_tlvs.authenticate(nonce, &expanded_key);
108109
let intermediate_nodes = [PaymentForwardNode {

fuzz/src/refund_deser.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ fn build_response<T: secp256k1::Signing + secp256k1::Verification>(
8080
htlc_minimum_msat: 1,
8181
},
8282
payment_context,
83+
custom_data: Vec::new(),
8384
};
8485
let payee_tlvs = payee_tlvs.authenticate(nonce, &expanded_key);
8586
let intermediate_nodes = [PaymentForwardNode {

lightning/src/blinded_path/message.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ impl Writeable for ReceiveTlvs {
281281
// TODO: write padding
282282
encode_tlv_stream!(writer, {
283283
(65537, self.context, option),
284-
(65539, self.custom_data, option)
284+
(65541, self.custom_data, option)
285285
});
286286
Ok(())
287287
}

lightning/src/blinded_path/payment.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,15 @@ pub struct UnauthenticatedReceiveTlvs {
283283
pub payment_constraints: PaymentConstraints,
284284
/// Context for the receiver of this payment.
285285
pub payment_context: PaymentContext,
286+
/// Custom data set by the user. And is returned back when the blinded path is used.
287+
///
288+
/// ## Note on Forward Compatibility:
289+
/// Users can encode any kind of data into the `Vec<u8>` bytes here. However, they should ensure
290+
/// that the data is structured in a forward-compatible manner. This is especially important as
291+
/// `ReceiveTlvs` created in one version of the software may still appear in payments received
292+
/// shortly after a software upgrade. Proper forward compatibility helps prevent data loss or
293+
/// misinterpretation in future versions.
294+
pub custom_data: Vec<u8>,
286295
}
287296

288297
impl UnauthenticatedReceiveTlvs {
@@ -444,6 +453,7 @@ impl Writeable for ReceiveTlvs {
444453
(65536, self.tlvs.payment_secret, required),
445454
(65537, self.tlvs.payment_context, required),
446455
(65539, self.authentication, required),
456+
(65541, self.tlvs.custom_data, required)
447457
});
448458
Ok(())
449459
}
@@ -455,6 +465,7 @@ impl Writeable for UnauthenticatedReceiveTlvs {
455465
(12, self.payment_constraints, required),
456466
(65536, self.payment_secret, required),
457467
(65537, self.payment_context, required),
468+
(65541, self.custom_data, (default_value, Vec::new())),
458469
});
459470
Ok(())
460471
}
@@ -483,6 +494,7 @@ impl Readable for BlindedPaymentTlvs {
483494
(65536, payment_secret, option),
484495
(65537, payment_context, option),
485496
(65539, authentication, option),
497+
(65541, custom_data, (default_value, Vec::new()))
486498
});
487499
let _padding: Option<utils::Padding> = _padding;
488500

@@ -504,6 +516,7 @@ impl Readable for BlindedPaymentTlvs {
504516
payment_secret: payment_secret.ok_or(DecodeError::InvalidValue)?,
505517
payment_constraints: payment_constraints.0.unwrap(),
506518
payment_context: payment_context.ok_or(DecodeError::InvalidValue)?,
519+
custom_data: custom_data.0.unwrap(),
507520
},
508521
authentication: authentication.ok_or(DecodeError::InvalidValue)?,
509522
}))
@@ -730,6 +743,7 @@ mod tests {
730743
htlc_minimum_msat: 1,
731744
},
732745
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}),
746+
custom_data: Vec::new(),
733747
};
734748
let htlc_maximum_msat = 100_000;
735749
let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, htlc_maximum_msat, 12).unwrap();
@@ -749,6 +763,7 @@ mod tests {
749763
htlc_minimum_msat: 1,
750764
},
751765
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}),
766+
custom_data: Vec::new(),
752767
};
753768
let blinded_payinfo = super::compute_payinfo(&[], &recv_tlvs, 4242, TEST_FINAL_CLTV as u16).unwrap();
754769
assert_eq!(blinded_payinfo.fee_base_msat, 0);
@@ -805,6 +820,7 @@ mod tests {
805820
htlc_minimum_msat: 3,
806821
},
807822
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}),
823+
custom_data: Vec::new(),
808824
};
809825
let htlc_maximum_msat = 100_000;
810826
let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, htlc_maximum_msat, TEST_FINAL_CLTV as u16).unwrap();
@@ -858,6 +874,7 @@ mod tests {
858874
htlc_minimum_msat: 1,
859875
},
860876
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}),
877+
custom_data: Vec::new(),
861878
};
862879
let htlc_minimum_msat = 3798;
863880
assert!(super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, htlc_minimum_msat - 1, TEST_FINAL_CLTV as u16).is_err());
@@ -915,6 +932,7 @@ mod tests {
915932
htlc_minimum_msat: 1,
916933
},
917934
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}),
935+
custom_data: Vec::new(),
918936
};
919937

920938
let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, 10_000, TEST_FINAL_CLTV as u16).unwrap();

lightning/src/ln/blinded_payment_tests.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ pub fn blinded_payment_path(
7676
intro_node_min_htlc_opt.unwrap_or_else(|| channel_upds.last().unwrap().htlc_minimum_msat),
7777
},
7878
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}),
79+
custom_data: Vec::new(),
7980
};
8081

8182
let nonce = Nonce([42u8; 16]);
@@ -127,6 +128,7 @@ fn do_one_hop_blinded_path(success: bool) {
127128
htlc_minimum_msat: chan_upd.htlc_minimum_msat,
128129
},
129130
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}),
131+
custom_data: Vec::new(),
130132
};
131133
let nonce = Nonce([42u8; 16]);
132134
let expanded_key = chanmon_cfgs[1].keys_manager.get_inbound_payment_key();
@@ -175,6 +177,7 @@ fn mpp_to_one_hop_blinded_path() {
175177
htlc_minimum_msat: chan_upd_1_3.htlc_minimum_msat,
176178
},
177179
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}),
180+
custom_data: Vec::new(),
178181
};
179182
let nonce = Nonce([42u8; 16]);
180183
let expanded_key = chanmon_cfgs[3].keys_manager.get_inbound_payment_key();
@@ -836,6 +839,8 @@ fn do_multi_hop_receiver_fail(check: ReceiveCheckFail) {
836839
let mut route_params = get_blinded_route_parameters(amt_msat, payment_secret, 1, 1_0000_0000,
837840
nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(), &[&chan_upd_1_2],
838841
&chanmon_cfgs[2].keys_manager);
842+
843+
route_params.payment_params.max_path_length = 18;
839844

840845
let route = if check == ReceiveCheckFail::ProcessPendingHTLCsCheck {
841846
let mut route = get_route(&nodes[0], &route_params).unwrap();
@@ -1240,6 +1245,7 @@ fn sender_custom_tlvs_to_blinded_path() {
12401245
htlc_minimum_msat: chan_upd.htlc_minimum_msat,
12411246
},
12421247
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}),
1248+
custom_data: Vec::new(),
12431249
};
12441250
let nonce = Nonce([42u8; 16]);
12451251
let expanded_key = chanmon_cfgs[1].keys_manager.get_inbound_payment_key();
@@ -1294,6 +1300,7 @@ fn fails_receive_tlvs_authentication() {
12941300
htlc_minimum_msat: chan_upd.htlc_minimum_msat,
12951301
},
12961302
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}),
1303+
custom_data: Vec::new(),
12971304
};
12981305
let nonce = Nonce([42u8; 16]);
12991306
let expanded_key = chanmon_cfgs[1].keys_manager.get_inbound_payment_key();
@@ -1325,6 +1332,7 @@ fn fails_receive_tlvs_authentication() {
13251332
htlc_minimum_msat: chan_upd.htlc_minimum_msat,
13261333
},
13271334
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}),
1335+
custom_data: Vec::new(),
13281336
};
13291337
let nonce = Nonce([43u8; 16]);
13301338
let mut payee_tlvs = payee_tlvs.authenticate(nonce, &expanded_key);

lightning/src/ln/channelmanager.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10243,7 +10243,7 @@ where
1024310243
Ok((payment_hash, payment_secret)) => {
1024410244
let payment_context = PaymentContext::Bolt12Refund(Bolt12RefundContext {});
1024510245
let payment_paths = self.create_blinded_payment_paths(
10246-
Some(amount_msats), payment_secret, payment_context, relative_expiry,
10246+
Some(amount_msats), payment_secret, payment_context, None, relative_expiry,
1024710247
)
1024810248
.map_err(|_| Bolt12SemanticError::MissingPaths)?;
1024910249

@@ -10562,7 +10562,7 @@ where
1056210562
/// Creates multi-hop blinded payment paths for the given `amount_msats` by delegating to
1056310563
/// [`Router::create_blinded_payment_paths`].
1056410564
fn create_blinded_payment_paths(
10565-
&self, amount_msats: Option<u64>, payment_secret: PaymentSecret, payment_context: PaymentContext,
10565+
&self, amount_msats: Option<u64>, payment_secret: PaymentSecret, payment_context: PaymentContext, custom_data: Option<Vec<u8>>,
1056610566
relative_expiry_seconds: u32
1056710567
) -> Result<Vec<BlindedPaymentPath>, ()> {
1056810568
let expanded_key = &self.inbound_payment_key;
@@ -10586,6 +10586,7 @@ where
1058610586
htlc_minimum_msat: 1,
1058710587
},
1058810588
payment_context,
10589+
custom_data: custom_data.unwrap_or_default()
1058910590
};
1059010591
let nonce = Nonce::from_entropy_source(entropy);
1059110592
let payee_tlvs = payee_tlvs.authenticate(nonce, expanded_key);
@@ -12123,7 +12124,7 @@ where
1212312124
invoice_request: invoice_request.fields(),
1212412125
});
1212512126
let payment_paths = match self.create_blinded_payment_paths(
12126-
Some(amount_msats), payment_secret, payment_context, relative_expiry
12127+
Some(amount_msats), payment_secret, payment_context, None, relative_expiry
1212712128
) {
1212812129
Ok(payment_paths) => payment_paths,
1212912130
Err(()) => {

lightning/src/ln/max_payment_path_len_tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ fn one_hop_blinded_path_with_custom_tlv() {
167167
htlc_minimum_msat: chan_upd_1_2.htlc_minimum_msat,
168168
},
169169
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}),
170+
custom_data: Vec::new(),
170171
};
171172
let nonce = Nonce([42u8; 16]);
172173
let expanded_key = chanmon_cfgs[2].keys_manager.get_inbound_payment_key();

lightning/src/ln/msgs.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2750,7 +2750,7 @@ impl<'a> Writeable for OutboundOnionPayload<'a> {
27502750
// We need to update [`ln::outbound_payment::RecipientOnionFields::with_sender_custom_tlvs`]
27512751
// to reject any reserved types in the experimental range if new ones are ever
27522752
// standardized.
2753-
let user_custom_data = (!user_custom_data.is_empty()).then(|| (65539, user_custom_data.to_vec()));
2753+
let user_custom_data = (!user_custom_data.is_empty()).then(|| (65541, user_custom_data.to_vec()));
27542754
let keysend_tlv = keysend_preimage.map(|preimage| (5482373484, preimage.encode()));
27552755
let mut custom_tlvs: Vec<&(u64, Vec<u8>)> = sender_custom_tlvs.iter().chain(keysend_tlv.iter()).chain(user_custom_data.iter()).collect();
27562756
custom_tlvs.sort_unstable_by_key(|(typ, _)| *typ);
@@ -2774,7 +2774,7 @@ impl<'a> Writeable for OutboundOnionPayload<'a> {
27742774
// We need to update [`ln::outbound_payment::RecipientOnionFields::with_sender_custom_tlvs`]
27752775
// to reject any reserved types in the experimental range if new ones are ever
27762776
// standardized.
2777-
let user_custom_data = (!user_custom_data.is_empty()).then(|| (65539, user_custom_data.to_vec()));
2777+
let user_custom_data = (!user_custom_data.is_empty()).then(|| (65541, user_custom_data.to_vec()));
27782778
let invoice_request_tlv = invoice_request.map(|invreq| (77_777, invreq.encode())); // TODO: update TLV type once the async payments spec is merged
27792779
let keysend_tlv = keysend_preimage.map(|preimage| (5482373484, preimage.encode()));
27802780
let mut custom_tlvs: Vec<&(u64, Vec<u8>)> = sender_custom_tlvs.iter()
@@ -2832,7 +2832,7 @@ impl<'a> Writeable for OutboundTrampolinePayload<'a> {
28322832
});
28332833
},
28342834
Self::BlindedReceive { sender_intended_htlc_amt_msat, total_msat, cltv_expiry_height, encrypted_tlvs, intro_node_blinding_point, keysend_preimage, sender_custom_tlvs, user_custom_data} => {
2835-
let user_custom_data = (!user_custom_data.is_empty()).then(|| (65539, user_custom_data.to_vec()));
2835+
let user_custom_data = (!user_custom_data.is_empty()).then(|| (65541, user_custom_data.to_vec()));
28362836
let custom_tlvs: Vec<&(u64, Vec<u8>)> = sender_custom_tlvs.iter()
28372837
.chain(user_custom_data.iter())
28382838
.collect();
@@ -2889,7 +2889,7 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundOnionPayload wh
28892889

28902890
let (user_custom_data, sender_custom_tlvs): (Vec<(u64, Vec<u8>)>, Vec<(u64, Vec<u8>)>) = custom_tlvs
28912891
.into_iter()
2892-
.partition(|(tlv_type, _)| *tlv_type == 65539);
2892+
.partition(|(tlv_type, _)| *tlv_type == 65541);
28932893

28942894
let user_custom_data = user_custom_data.into_iter().next().map(|(_, data)| data).unwrap_or_else(Vec::new);
28952895

@@ -2926,6 +2926,7 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundOnionPayload wh
29262926
next_blinding_override,
29272927
})
29282928
},
2929+
// Note: The custom data in the receive tlvs is not used here.
29292930
ChaChaPolyReadAdapter { readable: BlindedPaymentTlvs::Receive(receive_tlvs) } => {
29302931
let ReceiveTlvs { tlvs, authentication: (hmac, nonce) } = receive_tlvs;
29312932
let expanded_key = node_signer.get_inbound_payment_key();
@@ -2934,8 +2935,9 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundOnionPayload wh
29342935
}
29352936

29362937
let UnauthenticatedReceiveTlvs {
2937-
payment_secret, payment_constraints, payment_context,
2938+
payment_secret, payment_constraints, payment_context, custom_data
29382939
} = tlvs;
2940+
debug_assert_eq!(custom_data, user_custom_data, "The custom TLVs in ReceiveTlvs must match the ones read from serialization.");
29392941
if total_msat.unwrap_or(0) > MAX_VALUE_MSAT { return Err(DecodeError::InvalidValue) }
29402942
Ok(Self::BlindedReceive {
29412943
sender_intended_htlc_amt_msat: amt.ok_or(DecodeError::InvalidValue)?,

lightning/src/ln/payment_tests.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3834,8 +3834,8 @@ fn test_sender_custom_tlvs_consistency() {
38343834
let even_type_1 = 1 << 16;
38353835
let odd_type_1 = (1 << 16)+ 1;
38363836
let even_type_2 = (1 << 16) + 2;
3837-
// (1<<16) + 3 is reserved for user_custom_data.
3838-
let odd_type_2 = (1 << 16) + 5;
3837+
let odd_type_2 = (1 << 16) + 3;
3838+
// (1<<16) + 5 is reserved for user_custom_data.
38393839
let value_1 = || vec![1, 2, 3, 4];
38403840
let differing_value_1 = || vec![1, 2, 3, 5];
38413841
let value_2 = || vec![42u8; 16];

lightning/src/onion_message/packet.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ impl Readable for ControlTlvs {
347347
(4, next_node_id, option),
348348
(8, next_blinding_override, option),
349349
(65537, context, option),
350-
(65539, custom_data, option)
350+
(65541, custom_data, option)
351351
});
352352
let _padding: Option<Padding> = _padding;
353353

0 commit comments

Comments
 (0)