@@ -110,13 +110,17 @@ pub(super) enum PendingHTLCRouting {
110110 payment_metadata: Option<Vec<u8>>,
111111 incoming_cltv_expiry: u32, // Used to track when we should expire pending HTLCs that go unclaimed
112112 phantom_shared_secret: Option<[u8; 32]>,
113+ /// See [`RecipientOnionFields::custom_tlvs`] for more info.
114+ custom_tlvs: Vec<(u64, Vec<u8>)>,
113115 },
114116 ReceiveKeysend {
115117 /// This was added in 0.0.116 and will break deserialization on downgrades.
116118 payment_data: Option<msgs::FinalOnionHopData>,
117119 payment_preimage: PaymentPreimage,
118120 payment_metadata: Option<Vec<u8>>,
119121 incoming_cltv_expiry: u32, // Used to track when we should expire pending HTLCs that go unclaimed
122+ /// See [`RecipientOnionFields::custom_tlvs`] for more info.
123+ custom_tlvs: Vec<(u64, Vec<u8>)>,
120124 },
121125}
122126
@@ -355,15 +359,32 @@ struct InboundOnionErr {
355359pub enum FailureCode {
356360 /// We had a temporary error processing the payment. Useful if no other error codes fit
357361 /// and you want to indicate that the payer may want to retry.
358- TemporaryNodeFailure = 0x2000 | 2 ,
362+ TemporaryNodeFailure,
359363 /// We have a required feature which was not in this onion. For example, you may require
360364 /// some additional metadata that was not provided with this payment.
361- RequiredNodeFeatureMissing = 0x4000 | 0x2000 | 3 ,
365+ RequiredNodeFeatureMissing,
362366 /// You may wish to use this when a `payment_preimage` is unknown, or the CLTV expiry of
363367 /// the HTLC is too close to the current block height for safe handling.
364368 /// Using this failure code in [`ChannelManager::fail_htlc_backwards_with_reason`] is
365369 /// equivalent to calling [`ChannelManager::fail_htlc_backwards`].
366- IncorrectOrUnknownPaymentDetails = 0x4000 | 15 ,
370+ IncorrectOrUnknownPaymentDetails,
371+ /// We failed to process the payload after the onion was decrypted. You may wish to
372+ /// use this when receiving custom HTLC TLVs with even type numbers that you don't recognize.
373+ ///
374+ /// If available, the tuple data may include the type number and byte offset in the
375+ /// decrypted byte stream where the failure occurred.
376+ InvalidOnionPayload(Option<(u64, u16)>),
377+ }
378+
379+ impl Into<u16> for FailureCode {
380+ fn into(self) -> u16 {
381+ match self {
382+ FailureCode::TemporaryNodeFailure => 0x2000 | 2,
383+ FailureCode::RequiredNodeFeatureMissing => 0x4000 | 0x2000 | 3,
384+ FailureCode::IncorrectOrUnknownPaymentDetails => 0x4000 | 15,
385+ FailureCode::InvalidOnionPayload(_) => 0x4000 | 22,
386+ }
387+ }
367388}
368389
369390/// Error type returned across the peer_state mutex boundary. When an Err is generated for a
@@ -2674,11 +2695,11 @@ where
26742695 amt_msat: u64, cltv_expiry: u32, phantom_shared_secret: Option<[u8; 32]>, allow_underpay: bool,
26752696 counterparty_skimmed_fee_msat: Option<u64>,
26762697 ) -> Result<PendingHTLCInfo, InboundOnionErr> {
2677- let ( payment_data, keysend_preimage, onion_amt_msat, outgoing_cltv_value, payment_metadata) = match hop_data {
2698+ let (payment_data, keysend_preimage, custom_tlvs, onion_amt_msat, outgoing_cltv_value, payment_metadata) = match hop_data {
26782699 msgs::InboundOnionPayload::Receive {
2679- payment_data, keysend_preimage, amt_msat, outgoing_cltv_value, payment_metadata, ..
2700+ payment_data, keysend_preimage, custom_tlvs, amt_msat, outgoing_cltv_value, payment_metadata, ..
26802701 } =>
2681- ( payment_data, keysend_preimage, amt_msat, outgoing_cltv_value, payment_metadata) ,
2702+ (payment_data, keysend_preimage, custom_tlvs, amt_msat, outgoing_cltv_value, payment_metadata),
26822703 _ =>
26832704 return Err(InboundOnionErr {
26842705 err_code: 0x4000|22,
@@ -2748,13 +2769,15 @@ where
27482769 payment_preimage,
27492770 payment_metadata,
27502771 incoming_cltv_expiry: outgoing_cltv_value,
2772+ custom_tlvs,
27512773 }
27522774 } else if let Some(data) = payment_data {
27532775 PendingHTLCRouting::Receive {
27542776 payment_data: data,
27552777 payment_metadata,
27562778 incoming_cltv_expiry: outgoing_cltv_value,
27572779 phantom_shared_secret,
2780+ custom_tlvs,
27582781 }
27592782 } else {
27602783 return Err(InboundOnionErr {
@@ -3941,17 +3964,18 @@ where
39413964 }
39423965 }) => {
39433966 let (cltv_expiry, onion_payload, payment_data, phantom_shared_secret, mut onion_fields) = match routing {
3944- PendingHTLCRouting :: Receive { payment_data, payment_metadata, incoming_cltv_expiry, phantom_shared_secret } => {
3967+ PendingHTLCRouting::Receive { payment_data, payment_metadata, incoming_cltv_expiry, phantom_shared_secret, custom_tlvs } => {
39453968 let _legacy_hop_data = Some(payment_data.clone());
3946- let onion_fields =
3947- RecipientOnionFields { payment_secret : Some ( payment_data . payment_secret ) , payment_metadata } ;
3969+ let onion_fields = RecipientOnionFields { payment_secret: Some(payment_data.payment_secret),
3970+ payment_metadata, custom_tlvs };
39483971 (incoming_cltv_expiry, OnionPayload::Invoice { _legacy_hop_data },
39493972 Some(payment_data), phantom_shared_secret, onion_fields)
39503973 },
3951- PendingHTLCRouting :: ReceiveKeysend { payment_data, payment_preimage, payment_metadata, incoming_cltv_expiry } => {
3974+ PendingHTLCRouting::ReceiveKeysend { payment_data, payment_preimage, payment_metadata, incoming_cltv_expiry, custom_tlvs } => {
39523975 let onion_fields = RecipientOnionFields {
39533976 payment_secret: payment_data.as_ref().map(|data| data.payment_secret),
3954- payment_metadata
3977+ payment_metadata,
3978+ custom_tlvs,
39553979 };
39563980 (incoming_cltv_expiry, OnionPayload::Spontaneous(payment_preimage),
39573981 payment_data, None, onion_fields)
@@ -4576,12 +4600,19 @@ where
45764600 /// Gets error data to form an [`HTLCFailReason`] given a [`FailureCode`] and [`ClaimableHTLC`].
45774601 fn get_htlc_fail_reason_from_failure_code(&self, failure_code: FailureCode, htlc: &ClaimableHTLC) -> HTLCFailReason {
45784602 match failure_code {
4579- FailureCode :: TemporaryNodeFailure => HTLCFailReason :: from_failure_code ( failure_code as u16 ) ,
4580- FailureCode :: RequiredNodeFeatureMissing => HTLCFailReason :: from_failure_code ( failure_code as u16 ) ,
4603+ FailureCode::TemporaryNodeFailure => HTLCFailReason::from_failure_code(failure_code.into() ),
4604+ FailureCode::RequiredNodeFeatureMissing => HTLCFailReason::from_failure_code(failure_code.into() ),
45814605 FailureCode::IncorrectOrUnknownPaymentDetails => {
45824606 let mut htlc_msat_height_data = htlc.value.to_be_bytes().to_vec();
45834607 htlc_msat_height_data.extend_from_slice(&self.best_block.read().unwrap().height().to_be_bytes());
4584- HTLCFailReason :: reason ( failure_code as u16 , htlc_msat_height_data)
4608+ HTLCFailReason::reason(failure_code.into(), htlc_msat_height_data)
4609+ },
4610+ FailureCode::InvalidOnionPayload(data) => {
4611+ let fail_data = match data {
4612+ Some((typ, offset)) => [BigSize(typ).encode(), offset.encode()].concat(),
4613+ None => Vec::new(),
4614+ };
4615+ HTLCFailReason::reason(failure_code.into(), fail_data)
45854616 }
45864617 }
45874618 }
@@ -4728,13 +4759,35 @@ where
47284759 /// event matches your expectation. If you fail to do so and call this method, you may provide
47294760 /// the sender "proof-of-payment" when they did not fulfill the full expected payment.
47304761 ///
4762+ /// This function will fail the payment if it has custom TLVs with even type numbers, as we
4763+ /// will assume they are unknown. If you intend to accept even custom TLVs, you should use
4764+ /// [`claim_funds_with_known_custom_tlvs`].
4765+ ///
47314766 /// [`Event::PaymentClaimable`]: crate::events::Event::PaymentClaimable
47324767 /// [`Event::PaymentClaimable::claim_deadline`]: crate::events::Event::PaymentClaimable::claim_deadline
47334768 /// [`Event::PaymentClaimed`]: crate::events::Event::PaymentClaimed
47344769 /// [`process_pending_events`]: EventsProvider::process_pending_events
47354770 /// [`create_inbound_payment`]: Self::create_inbound_payment
47364771 /// [`create_inbound_payment_for_hash`]: Self::create_inbound_payment_for_hash
4772+ /// [`claim_funds_with_known_custom_tlvs`]: Self::claim_funds_with_known_custom_tlvs
47374773 pub fn claim_funds(&self, payment_preimage: PaymentPreimage) {
4774+ self.claim_payment_internal(payment_preimage, false);
4775+ }
4776+
4777+ /// This is a variant of [`claim_funds`] that allows accepting a payment with custom TLVs with
4778+ /// even type numbers.
4779+ ///
4780+ /// # Note
4781+ ///
4782+ /// You MUST check you've understood all even TLVs before using this to
4783+ /// claim, otherwise you may unintentionally agree to some protocol you do not understand.
4784+ ///
4785+ /// [`claim_funds`]: Self::claim_funds
4786+ pub fn claim_funds_with_known_custom_tlvs(&self, payment_preimage: PaymentPreimage) {
4787+ self.claim_payment_internal(payment_preimage, true);
4788+ }
4789+
4790+ fn claim_payment_internal(&self, payment_preimage: PaymentPreimage, custom_tlvs_known: bool) {
47384791 let payment_hash = PaymentHash(Sha256::hash(&payment_preimage.0).into_inner());
47394792
47404793 let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
@@ -4761,6 +4814,23 @@ where
47614814 log_error!(self.logger, "Got a duplicate pending claimable event on payment hash {}! Please report this bug",
47624815 log_bytes!(payment_hash.0));
47634816 }
4817+
4818+ if let Some(RecipientOnionFields { ref custom_tlvs, .. }) = payment.onion_fields {
4819+ if !custom_tlvs_known && custom_tlvs.iter().any(|(typ, _)| typ % 2 == 0) {
4820+ log_info!(self.logger, "Rejecting payment with payment hash {} as we cannot accept payment with unknown even TLVs: {}",
4821+ log_bytes!(payment_hash.0), log_iter!(custom_tlvs.iter().map(|(typ, _)| typ).filter(|typ| *typ % 2 == 0)));
4822+ claimable_payments.pending_claiming_payments.remove(&payment_hash);
4823+ mem::drop(claimable_payments);
4824+ for htlc in payment.htlcs {
4825+ let reason = self.get_htlc_fail_reason_from_failure_code(FailureCode::InvalidOnionPayload(None), &htlc);
4826+ let source = HTLCSource::PreviousHopData(htlc.prev_hop);
4827+ let receiver = HTLCDestination::FailedPayment { payment_hash };
4828+ self.fail_htlc_backwards_internal(&source, &payment_hash, &reason, receiver);
4829+ }
4830+ return;
4831+ }
4832+ }
4833+
47644834 payment.htlcs
47654835 } else { return; }
47664836 };
@@ -7638,12 +7708,14 @@ impl_writeable_tlv_based_enum!(PendingHTLCRouting,
76387708 (1, phantom_shared_secret, option),
76397709 (2, incoming_cltv_expiry, required),
76407710 (3, payment_metadata, option),
7711+ (5, custom_tlvs, optional_vec),
76417712 },
76427713 (2, ReceiveKeysend) => {
76437714 (0, payment_preimage, required),
76447715 (2, incoming_cltv_expiry, required),
76457716 (3, payment_metadata, option),
76467717 (4, payment_data, option), // Added in 0.0.116
7718+ (5, custom_tlvs, optional_vec),
76477719 },
76487720;);
76497721
@@ -8750,6 +8822,7 @@ where
87508822 payment_secret: None, // only used for retries, and we'll never retry on startup
87518823 payment_metadata: None, // only used for retries, and we'll never retry on startup
87528824 keysend_preimage: None, // only used for retries, and we'll never retry on startup
8825+ custom_tlvs: Vec::new(), // only used for retries, and we'll never retry on startup
87538826 pending_amt_msat: path_amt,
87548827 pending_fee_msat: Some(path_fee),
87558828 total_msat: path_amt,
@@ -10092,6 +10165,7 @@ mod tests {
1009210165 payment_data: Some(msgs::FinalOnionHopData {
1009310166 payment_secret: PaymentSecret([0; 32]), total_msat: sender_intended_amt_msat,
1009410167 }),
10168+ custom_tlvs: Vec::new(),
1009510169 };
1009610170 // Check that if the amount we received + the penultimate hop extra fee is less than the sender
1009710171 // intended amount, we fail the payment.
@@ -10111,6 +10185,7 @@ mod tests {
1011110185 payment_data: Some(msgs::FinalOnionHopData {
1011210186 payment_secret: PaymentSecret([0; 32]), total_msat: sender_intended_amt_msat,
1011310187 }),
10188+ custom_tlvs: Vec::new(),
1011410189 };
1011510190 assert!(node[0].node.construct_recv_pending_htlc_info(hop_data, [0; 32], PaymentHash([0; 32]),
1011610191 sender_intended_amt_msat - extra_fee_msat, 42, None, true, Some(extra_fee_msat)).is_ok());
0 commit comments