Skip to content

Commit 3ddac04

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 d708afc commit 3ddac04

File tree

6 files changed

+77
-17
lines changed

6 files changed

+77
-17
lines changed

lightning/src/blinded_path/payment.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -638,11 +638,15 @@ impl_writeable_tlv_based!(Bolt12RefundContext, {});
638638

639639
#[cfg(test)]
640640
mod tests {
641+
use bitcoin::hashes::hmac::Hmac;
642+
use bitcoin::hashes::sha256::Hash as Sha256;
643+
use bitcoin::hashes::Hash;
641644
use bitcoin::secp256k1::PublicKey;
642645
use crate::blinded_path::payment::{PaymentForwardNode, ForwardTlvs, ReceiveTlvs, PaymentConstraints, PaymentContext, PaymentRelay};
643646
use crate::types::payment::PaymentSecret;
644647
use crate::types::features::BlindedHopFeatures;
645648
use crate::ln::functional_test_utils::TEST_FINAL_CLTV;
649+
use crate::offers::nonce::Nonce;
646650

647651
#[test]
648652
fn compute_payinfo() {
@@ -691,6 +695,7 @@ mod tests {
691695
htlc_minimum_msat: 1,
692696
},
693697
payment_context: PaymentContext::unknown(),
698+
authentication: (Hmac::<Sha256>::hash(&[42u8]), Nonce([42u8; 16])),
694699
};
695700
let htlc_maximum_msat = 100_000;
696701
let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, htlc_maximum_msat, 12).unwrap();
@@ -710,6 +715,7 @@ mod tests {
710715
htlc_minimum_msat: 1,
711716
},
712717
payment_context: PaymentContext::unknown(),
718+
authentication: (Hmac::<Sha256>::hash(&[42u8]), Nonce([42u8; 16])),
713719
};
714720
let blinded_payinfo = super::compute_payinfo(&[], &recv_tlvs, 4242, TEST_FINAL_CLTV as u16).unwrap();
715721
assert_eq!(blinded_payinfo.fee_base_msat, 0);
@@ -766,6 +772,7 @@ mod tests {
766772
htlc_minimum_msat: 3,
767773
},
768774
payment_context: PaymentContext::unknown(),
775+
authentication: (Hmac::<Sha256>::hash(&[42u8]), Nonce([42u8; 16])),
769776
};
770777
let htlc_maximum_msat = 100_000;
771778
let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, htlc_maximum_msat, TEST_FINAL_CLTV as u16).unwrap();
@@ -819,6 +826,7 @@ mod tests {
819826
htlc_minimum_msat: 1,
820827
},
821828
payment_context: PaymentContext::unknown(),
829+
authentication: (Hmac::<Sha256>::hash(&[42u8]), Nonce([42u8; 16])),
822830
};
823831
let htlc_minimum_msat = 3798;
824832
assert!(super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, htlc_minimum_msat - 1, TEST_FINAL_CLTV as u16).is_err());
@@ -876,6 +884,7 @@ mod tests {
876884
htlc_minimum_msat: 1,
877885
},
878886
payment_context: PaymentContext::unknown(),
887+
authentication: (Hmac::<Sha256>::hash(&[42u8]), Nonce([42u8; 16])),
879888
};
880889

881890
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: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
// You may not use this file except in accordance with one or both of these
88
// licenses.
99

10+
use bitcoin::hashes::hmac::Hmac;
1011
use bitcoin::hashes::hex::FromHex;
12+
use bitcoin::hashes::sha256::Hash as Sha256;
1113
use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey, schnorr};
1214
use bitcoin::secp256k1::ecdh::SharedSecret;
1315
use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature};
@@ -17,16 +19,18 @@ use crate::events::{Event, HTLCDestination, MessageSendEvent, MessageSendEventsP
1719
use crate::ln::types::ChannelId;
1820
use crate::types::payment::{PaymentHash, PaymentSecret};
1921
use crate::ln::channelmanager;
20-
use crate::ln::channelmanager::{HTLCFailureMsg, PaymentId, RecipientOnionFields};
22+
use crate::ln::channelmanager::{HTLCFailureMsg, PaymentId, RecipientOnionFields, Verification};
2123
use crate::types::features::{BlindedHopFeatures, ChannelFeatures, NodeFeatures};
2224
use crate::ln::functional_test_utils::*;
25+
use crate::ln::inbound_payment::ExpandedKey;
2326
use crate::ln::msgs;
2427
use crate::ln::msgs::{ChannelMessageHandler, UnsignedGossipMessage};
2528
use crate::ln::onion_payment;
2629
use crate::ln::onion_utils;
2730
use crate::ln::onion_utils::INVALID_ONION_BLINDING;
2831
use crate::ln::outbound_payment::{Retry, IDEMPOTENCY_TIMEOUT_TICKS};
2932
use crate::offers::invoice::UnsignedBolt12Invoice;
33+
use crate::offers::nonce::Nonce;
3034
use crate::prelude::*;
3135
use crate::routing::router::{BlindedTail, Path, Payee, PaymentParameters, RouteHop, RouteParameters};
3236
use crate::sign::{KeyMaterial, NodeSigner, Recipient};
@@ -69,15 +73,19 @@ fn blinded_payment_path(
6973
.unwrap_or_else(|| channel_upds[idx - 1].htlc_maximum_msat),
7074
});
7175
}
76+
77+
let payment_context = PaymentContext::unknown();
7278
let payee_tlvs = ReceiveTlvs {
7379
payment_secret,
7480
payment_constraints: PaymentConstraints {
7581
max_cltv_expiry: u32::max_value(),
7682
htlc_minimum_msat:
7783
intro_node_min_htlc_opt.unwrap_or_else(|| channel_upds.last().unwrap().htlc_minimum_msat),
7884
},
79-
payment_context: PaymentContext::unknown(),
85+
authentication: hmac_payment_context(&payment_context, keys_manager),
86+
payment_context,
8087
};
88+
8189
let mut secp_ctx = Secp256k1::new();
8290
BlindedPaymentPath::new(
8391
&intermediate_nodes[..], *node_ids.last().unwrap(), payee_tlvs,
@@ -86,6 +94,15 @@ fn blinded_payment_path(
8694
).unwrap()
8795
}
8896

97+
fn hmac_payment_context(
98+
payment_context: &PaymentContext, keys_manager: &test_utils::TestKeysInterface,
99+
) -> (Hmac<Sha256>, Nonce) {
100+
let nonce = Nonce([42u8; 16]);
101+
let expanded_key = ExpandedKey::new(&keys_manager.get_inbound_payment_key_material());
102+
let hmac = payment_context.hmac_for_offer_payment(nonce, &expanded_key);
103+
(hmac, nonce)
104+
}
105+
89106
pub fn get_blinded_route_parameters(
90107
amt_msat: u64, payment_secret: PaymentSecret, intro_node_min_htlc: u64, intro_node_max_htlc: u64,
91108
node_ids: Vec<PublicKey>, channel_upds: &[&msgs::UnsignedChannelUpdate],
@@ -116,13 +133,15 @@ fn do_one_hop_blinded_path(success: bool) {
116133

117134
let amt_msat = 5000;
118135
let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[1], Some(amt_msat), None);
136+
let payment_context = PaymentContext::unknown();
119137
let payee_tlvs = ReceiveTlvs {
120138
payment_secret,
121139
payment_constraints: PaymentConstraints {
122140
max_cltv_expiry: u32::max_value(),
123141
htlc_minimum_msat: chan_upd.htlc_minimum_msat,
124142
},
125-
payment_context: PaymentContext::unknown(),
143+
authentication: hmac_payment_context(&payment_context, &chanmon_cfgs[1].keys_manager),
144+
payment_context,
126145
};
127146
let mut secp_ctx = Secp256k1::new();
128147
let blinded_path = BlindedPaymentPath::new(
@@ -160,13 +179,15 @@ fn mpp_to_one_hop_blinded_path() {
160179

161180
let amt_msat = 15_000_000;
162181
let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[3], Some(amt_msat), None);
182+
let payment_context = PaymentContext::unknown();
163183
let payee_tlvs = ReceiveTlvs {
164184
payment_secret,
165185
payment_constraints: PaymentConstraints {
166186
max_cltv_expiry: u32::max_value(),
167187
htlc_minimum_msat: chan_upd_1_3.htlc_minimum_msat,
168188
},
169-
payment_context: PaymentContext::unknown(),
189+
authentication: hmac_payment_context(&payment_context, &chanmon_cfgs[3].keys_manager),
190+
payment_context,
170191
};
171192
let blinded_path = BlindedPaymentPath::new(
172193
&[], nodes[3].node.get_our_node_id(), payee_tlvs, u64::MAX, TEST_FINAL_CLTV as u16,
@@ -302,7 +323,7 @@ fn do_forward_checks_failure(check: ForwardCheckFail, intro_fails: bool) {
302323
let mut route_params = get_blinded_route_parameters(amt_msat, payment_secret, 1, 1_0000_0000,
303324
nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(),
304325
&[&chan_upd_1_2, &chan_upd_2_3], &chanmon_cfgs[3].keys_manager);
305-
route_params.payment_params.max_path_length = 18;
326+
route_params.payment_params.max_path_length = 17;
306327

307328
let route = get_route(&nodes[0], &route_params).unwrap();
308329
node_cfgs[0].router.expect_find_route(route_params.clone(), Ok(route.clone()));
@@ -1375,13 +1396,15 @@ fn custom_tlvs_to_blinded_path() {
13751396

13761397
let amt_msat = 5000;
13771398
let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[1], Some(amt_msat), None);
1399+
let payment_context = PaymentContext::unknown();
13781400
let payee_tlvs = ReceiveTlvs {
13791401
payment_secret,
13801402
payment_constraints: PaymentConstraints {
13811403
max_cltv_expiry: u32::max_value(),
13821404
htlc_minimum_msat: chan_upd.htlc_minimum_msat,
13831405
},
1384-
payment_context: PaymentContext::unknown(),
1406+
authentication: hmac_payment_context(&payment_context, &chanmon_cfgs[1].keys_manager),
1407+
payment_context,
13851408
};
13861409
let mut secp_ctx = Secp256k1::new();
13871410
let blinded_path = BlindedPaymentPath::new(

lightning/src/ln/channelmanager.rs

Lines changed: 17 additions & 4 deletions
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
@@ -5995,19 +5997,19 @@ where
59955997
let blinded_failure = routing.blinded_failure();
59965998
let (
59975999
cltv_expiry, onion_payload, payment_data, payment_context, phantom_shared_secret,
5998-
mut onion_fields, has_recipient_created_payment_secret
6000+
mut onion_fields, has_recipient_created_payment_secret, authentication,
59996001
) = match routing {
60006002
PendingHTLCRouting::Receive {
60016003
payment_data, payment_metadata, payment_context,
60026004
incoming_cltv_expiry, phantom_shared_secret, custom_tlvs,
6003-
requires_blinded_error: _
6005+
requires_blinded_error: _, authentication,
60046006
} => {
60056007
let _legacy_hop_data = Some(payment_data.clone());
60066008
let onion_fields = RecipientOnionFields { payment_secret: Some(payment_data.payment_secret),
60076009
payment_metadata, custom_tlvs };
60086010
(incoming_cltv_expiry, OnionPayload::Invoice { _legacy_hop_data },
60096011
Some(payment_data), payment_context, phantom_shared_secret, onion_fields,
6010-
true)
6012+
true, authentication)
60116013
},
60126014
PendingHTLCRouting::ReceiveKeysend {
60136015
payment_data, payment_preimage, payment_metadata,
@@ -6020,7 +6022,7 @@ where
60206022
custom_tlvs,
60216023
};
60226024
(incoming_cltv_expiry, OnionPayload::Spontaneous(payment_preimage),
6023-
payment_data, None, None, onion_fields, has_recipient_created_payment_secret)
6025+
payment_data, None, None, onion_fields, has_recipient_created_payment_secret, None)
60246026
},
60256027
_ => {
60266028
panic!("short_channel_id == 0 should imply any pending_forward entries are of type Receive");
@@ -6205,6 +6207,16 @@ where
62056207
payment_preimage
62066208
} else { fail_htlc!(claimable_htlc, payment_hash); }
62076209
} else { None };
6210+
6211+
// Authenticate the PaymentContext received over a BlindedPaymentPath
6212+
if let Some(payment_context) = payment_context.as_ref() {
6213+
if let Some((hmac, nonce)) = authentication {
6214+
if payment_context.verify_for_offer_payment(hmac, nonce, &self.inbound_payment_key).is_err() {
6215+
fail_htlc!(claimable_htlc, payment_hash);
6216+
}
6217+
}
6218+
}
6219+
62086220
match claimable_htlc.onion_payload {
62096221
OnionPayload::Invoice { .. } => {
62106222
let payment_data = payment_data.unwrap();
@@ -12363,6 +12375,7 @@ impl_writeable_tlv_based_enum!(PendingHTLCRouting,
1236312375
(5, custom_tlvs, optional_vec),
1236412376
(7, requires_blinded_error, (default_value, false)),
1236512377
(9, payment_context, option),
12378+
(11, authentication, option),
1236612379
},
1236712380
(2, ReceiveKeysend) => {
1236812381
(0, payment_preimage, required),

lightning/src/ln/max_payment_path_len_tests.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,19 @@ use crate::blinded_path::payment::{BlindedPayInfo, BlindedPaymentPath, PaymentCo
1616
use crate::events::{Event, MessageSendEventsProvider};
1717
use crate::types::payment::PaymentSecret;
1818
use crate::ln::blinded_payment_tests::get_blinded_route_parameters;
19-
use crate::ln::channelmanager::PaymentId;
19+
use crate::ln::channelmanager::{PaymentId, Verification};
2020
use crate::types::features::BlindedHopFeatures;
2121
use crate::ln::functional_test_utils::*;
22+
use crate::ln::inbound_payment::ExpandedKey;
2223
use crate::ln::msgs;
2324
use crate::ln::msgs::OnionMessageHandler;
2425
use crate::ln::onion_utils;
2526
use crate::ln::onion_utils::MIN_FINAL_VALUE_ESTIMATE_WITH_OVERPAY;
2627
use crate::ln::outbound_payment::{RecipientOnionFields, Retry, RetryableSendFailure};
28+
use crate::offers::nonce::Nonce;
2729
use crate::prelude::*;
2830
use crate::routing::router::{DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA, PaymentParameters, RouteParameters};
31+
use crate::sign::NodeSigner;
2932
use crate::util::errors::APIError;
3033
use crate::util::ser::Writeable;
3134
use crate::util::test_utils;
@@ -157,13 +160,18 @@ fn one_hop_blinded_path_with_custom_tlv() {
157160
// Construct the route parameters for sending to nodes[2]'s 1-hop blinded path.
158161
let amt_msat = 100_000;
159162
let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None);
163+
let payment_context = PaymentContext::unknown();
164+
let nonce = Nonce([42u8; 16]);
165+
let expanded_key = ExpandedKey::new(&chanmon_cfgs[2].keys_manager.get_inbound_payment_key_material());
166+
let hmac = payment_context.hmac_for_offer_payment(nonce, &expanded_key);
160167
let payee_tlvs = ReceiveTlvs {
161168
payment_secret,
162169
payment_constraints: PaymentConstraints {
163170
max_cltv_expiry: u32::max_value(),
164171
htlc_minimum_msat: chan_upd_1_2.htlc_minimum_msat,
165172
},
166-
payment_context: PaymentContext::unknown(),
173+
payment_context,
174+
authentication: (hmac, nonce),
167175
};
168176
let mut secp_ctx = Secp256k1::new();
169177
let blinded_path = BlindedPaymentPath::new(

lightning/src/ln/msgs.rs

Lines changed: 6 additions & 1 deletion
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

Lines changed: 6 additions & 4 deletions
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)