Skip to content

Commit 9e0fc23

Browse files
committed
Verify that an HTLC's PaymentContext is authentic
When receiving a payment over a BlindedPaymentPath, a PaymentContext is included but was not authenticated. The previous commit adds an HMAC of the PaymentContext to the payment::ReceiveTlvs and the nonce used to create the HMAC. This commit pipes this data through to ChannelManager in order to verify the PaymentContext's authenticity. This prevents a malicious actor from for forging it.
1 parent 0016a51 commit 9e0fc23

File tree

3 files changed

+29
-9
lines changed

3 files changed

+29
-9
lines changed

lightning/src/ln/channelmanager.rs

+17-4
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,8 @@ pub enum PendingHTLCRouting {
198198
custom_tlvs: Vec<(u64, Vec<u8>)>,
199199
/// Set if this HTLC is the final hop in a multi-hop blinded path.
200200
requires_blinded_error: bool,
201+
/// An HMAC of `payment_context` along with a nonce used to construct it.
202+
authentication: Option<(Hmac<Sha256>, Nonce)>,
201203
},
202204
/// The onion indicates that this is for payment to us but which contains the preimage for
203205
/// claiming included, and is unrelated to any invoice we'd previously generated (aka a
@@ -5994,19 +5996,19 @@ where
59945996
let blinded_failure = routing.blinded_failure();
59955997
let (
59965998
cltv_expiry, onion_payload, payment_data, payment_context, phantom_shared_secret,
5997-
mut onion_fields, has_recipient_created_payment_secret
5999+
mut onion_fields, has_recipient_created_payment_secret, authentication,
59986000
) = match routing {
59996001
PendingHTLCRouting::Receive {
60006002
payment_data, payment_metadata, payment_context,
60016003
incoming_cltv_expiry, phantom_shared_secret, custom_tlvs,
6002-
requires_blinded_error: _
6004+
requires_blinded_error: _, authentication,
60036005
} => {
60046006
let _legacy_hop_data = Some(payment_data.clone());
60056007
let onion_fields = RecipientOnionFields { payment_secret: Some(payment_data.payment_secret),
60066008
payment_metadata, custom_tlvs };
60076009
(incoming_cltv_expiry, OnionPayload::Invoice { _legacy_hop_data },
60086010
Some(payment_data), payment_context, phantom_shared_secret, onion_fields,
6009-
true)
6011+
true, authentication)
60106012
},
60116013
PendingHTLCRouting::ReceiveKeysend {
60126014
payment_data, payment_preimage, payment_metadata,
@@ -6019,7 +6021,7 @@ where
60196021
custom_tlvs,
60206022
};
60216023
(incoming_cltv_expiry, OnionPayload::Spontaneous(payment_preimage),
6022-
payment_data, None, None, onion_fields, has_recipient_created_payment_secret)
6024+
payment_data, None, None, onion_fields, has_recipient_created_payment_secret, None)
60236025
},
60246026
_ => {
60256027
panic!("short_channel_id == 0 should imply any pending_forward entries are of type Receive");
@@ -6204,6 +6206,16 @@ where
62046206
payment_preimage
62056207
} else { fail_htlc!(claimable_htlc, payment_hash); }
62066208
} else { None };
6209+
6210+
// Authenticate the PaymentContext received over a BlindedPaymentPath
6211+
if let Some(payment_context) = payment_context.as_ref() {
6212+
if let Some((hmac, nonce)) = authentication {
6213+
if payment_context.verify_for_offer_payment(hmac, nonce, &self.inbound_payment_key).is_err() {
6214+
fail_htlc!(claimable_htlc, payment_hash);
6215+
}
6216+
}
6217+
}
6218+
62076219
match claimable_htlc.onion_payload {
62086220
OnionPayload::Invoice { .. } => {
62096221
let payment_data = payment_data.unwrap();
@@ -12362,6 +12374,7 @@ impl_writeable_tlv_based_enum!(PendingHTLCRouting,
1236212374
(5, custom_tlvs, optional_vec),
1236312375
(7, requires_blinded_error, (default_value, false)),
1236412376
(9, payment_context, option),
12377+
(11, authentication, option),
1236512378
},
1236612379
(2, ReceiveKeysend) => {
1236712380
(0, payment_preimage, required),

lightning/src/ln/msgs.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -1745,9 +1745,12 @@ pub struct FinalOnionHopData {
17451745
}
17461746

17471747
mod fuzzy_internal_msgs {
1748+
use bitcoin::hashes::hmac::Hmac;
1749+
use bitcoin::hashes::sha256::Hash as Sha256;
17481750
use bitcoin::secp256k1::PublicKey;
17491751
use crate::blinded_path::payment::{BlindedPaymentPath, PaymentConstraints, PaymentContext, PaymentRelay};
17501752
use crate::offers::invoice_request::InvoiceRequest;
1753+
use crate::offers::nonce::Nonce;
17511754
use crate::types::payment::{PaymentPreimage, PaymentSecret};
17521755
use crate::types::features::{BlindedHopFeatures, Bolt12InvoiceFeatures};
17531756
use super::{FinalOnionHopData, TrampolineOnionPacket};
@@ -1791,6 +1794,7 @@ mod fuzzy_internal_msgs {
17911794
intro_node_blinding_point: Option<PublicKey>,
17921795
keysend_preimage: Option<PaymentPreimage>,
17931796
custom_tlvs: Vec<(u64, Vec<u8>)>,
1797+
authentication: (Hmac<Sha256>, Nonce),
17941798
}
17951799
}
17961800

@@ -2908,7 +2912,7 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundOnionPayload wh
29082912
})
29092913
},
29102914
ChaChaPolyReadAdapter { readable: BlindedPaymentTlvs::Receive(ReceiveTlvs {
2911-
payment_secret, payment_constraints, payment_context, authentication: _,
2915+
payment_secret, payment_constraints, payment_context, authentication,
29122916
})} => {
29132917
if total_msat.unwrap_or(0) > MAX_VALUE_MSAT { return Err(DecodeError::InvalidValue) }
29142918
Ok(Self::BlindedReceive {
@@ -2921,6 +2925,7 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundOnionPayload wh
29212925
intro_node_blinding_point,
29222926
keysend_preimage,
29232927
custom_tlvs,
2928+
authentication,
29242929
})
29252930
},
29262931
}

lightning/src/ln/onion_payment.rs

+6-4
Original file line numberDiff line numberDiff line change
@@ -135,18 +135,19 @@ pub(super) fn create_recv_pending_htlc_info(
135135
) -> Result<PendingHTLCInfo, InboundHTLCErr> {
136136
let (
137137
payment_data, keysend_preimage, custom_tlvs, onion_amt_msat, onion_cltv_expiry,
138-
payment_metadata, payment_context, requires_blinded_error, has_recipient_created_payment_secret
138+
payment_metadata, payment_context, requires_blinded_error, has_recipient_created_payment_secret,
139+
authentication,
139140
) = match hop_data {
140141
msgs::InboundOnionPayload::Receive {
141142
payment_data, keysend_preimage, custom_tlvs, sender_intended_htlc_amt_msat,
142143
cltv_expiry_height, payment_metadata, ..
143144
} =>
144145
(payment_data, keysend_preimage, custom_tlvs, sender_intended_htlc_amt_msat,
145-
cltv_expiry_height, payment_metadata, None, false, keysend_preimage.is_none()),
146+
cltv_expiry_height, payment_metadata, None, false, keysend_preimage.is_none(), None),
146147
msgs::InboundOnionPayload::BlindedReceive {
147148
sender_intended_htlc_amt_msat, total_msat, cltv_expiry_height, payment_secret,
148149
intro_node_blinding_point, payment_constraints, payment_context, keysend_preimage,
149-
custom_tlvs
150+
custom_tlvs, authentication,
150151
} => {
151152
check_blinded_payment_constraints(
152153
sender_intended_htlc_amt_msat, cltv_expiry, &payment_constraints
@@ -161,7 +162,7 @@ pub(super) fn create_recv_pending_htlc_info(
161162
let payment_data = msgs::FinalOnionHopData { payment_secret, total_msat };
162163
(Some(payment_data), keysend_preimage, custom_tlvs,
163164
sender_intended_htlc_amt_msat, cltv_expiry_height, None, Some(payment_context),
164-
intro_node_blinding_point.is_none(), true)
165+
intro_node_blinding_point.is_none(), true, Some(authentication))
165166
}
166167
msgs::InboundOnionPayload::Forward { .. } => {
167168
return Err(InboundHTLCErr {
@@ -252,6 +253,7 @@ pub(super) fn create_recv_pending_htlc_info(
252253
phantom_shared_secret,
253254
custom_tlvs,
254255
requires_blinded_error,
256+
authentication,
255257
}
256258
} else {
257259
return Err(InboundHTLCErr {

0 commit comments

Comments
 (0)