Skip to content

Commit

Permalink
Verify that an HTLC's PaymentContext is authentic
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jkczyz committed Dec 4, 2024
1 parent efa1948 commit 0ca5402
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 9 deletions.
21 changes: 17 additions & 4 deletions lightning/src/ln/channelmanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,8 @@ pub enum PendingHTLCRouting {
custom_tlvs: Vec<(u64, Vec<u8>)>,
/// Set if this HTLC is the final hop in a multi-hop blinded path.
requires_blinded_error: bool,
/// An HMAC of `payment_context` along with a nonce used to construct it.
authentication: Option<(Hmac<Sha256>, Nonce)>,
},
/// The onion indicates that this is for payment to us but which contains the preimage for
/// claiming included, and is unrelated to any invoice we'd previously generated (aka a
Expand Down Expand Up @@ -5994,19 +5996,19 @@ where
let blinded_failure = routing.blinded_failure();
let (
cltv_expiry, onion_payload, payment_data, payment_context, phantom_shared_secret,
mut onion_fields, has_recipient_created_payment_secret
mut onion_fields, has_recipient_created_payment_secret, authentication,
) = match routing {
PendingHTLCRouting::Receive {
payment_data, payment_metadata, payment_context,
incoming_cltv_expiry, phantom_shared_secret, custom_tlvs,
requires_blinded_error: _
requires_blinded_error: _, authentication,
} => {
let _legacy_hop_data = Some(payment_data.clone());
let onion_fields = RecipientOnionFields { payment_secret: Some(payment_data.payment_secret),
payment_metadata, custom_tlvs };
(incoming_cltv_expiry, OnionPayload::Invoice { _legacy_hop_data },
Some(payment_data), payment_context, phantom_shared_secret, onion_fields,
true)
true, authentication)
},
PendingHTLCRouting::ReceiveKeysend {
payment_data, payment_preimage, payment_metadata,
Expand All @@ -6019,7 +6021,7 @@ where
custom_tlvs,
};
(incoming_cltv_expiry, OnionPayload::Spontaneous(payment_preimage),
payment_data, None, None, onion_fields, has_recipient_created_payment_secret)
payment_data, None, None, onion_fields, has_recipient_created_payment_secret, None)
},
_ => {
panic!("short_channel_id == 0 should imply any pending_forward entries are of type Receive");
Expand Down Expand Up @@ -6204,6 +6206,16 @@ where
payment_preimage
} else { fail_htlc!(claimable_htlc, payment_hash); }
} else { None };

// Authenticate the PaymentContext received over a BlindedPaymentPath
if let Some(payment_context) = payment_context.as_ref() {
if let Some((hmac, nonce)) = authentication {
if payment_context.verify_for_offer_payment(hmac, nonce, &self.inbound_payment_key).is_err() {
fail_htlc!(claimable_htlc, payment_hash);
}
}
}

match claimable_htlc.onion_payload {
OnionPayload::Invoice { .. } => {
let payment_data = payment_data.unwrap();
Expand Down Expand Up @@ -12362,6 +12374,7 @@ impl_writeable_tlv_based_enum!(PendingHTLCRouting,
(5, custom_tlvs, optional_vec),
(7, requires_blinded_error, (default_value, false)),
(9, payment_context, option),
(11, authentication, option),
},
(2, ReceiveKeysend) => {
(0, payment_preimage, required),
Expand Down
7 changes: 6 additions & 1 deletion lightning/src/ln/msgs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1745,9 +1745,12 @@ pub struct FinalOnionHopData {
}

mod fuzzy_internal_msgs {
use bitcoin::hashes::hmac::Hmac;
use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::secp256k1::PublicKey;
use crate::blinded_path::payment::{BlindedPaymentPath, PaymentConstraints, PaymentContext, PaymentRelay};
use crate::offers::invoice_request::InvoiceRequest;
use crate::offers::nonce::Nonce;
use crate::types::payment::{PaymentPreimage, PaymentSecret};
use crate::types::features::{BlindedHopFeatures, Bolt12InvoiceFeatures};
use super::{FinalOnionHopData, TrampolineOnionPacket};
Expand Down Expand Up @@ -1791,6 +1794,7 @@ mod fuzzy_internal_msgs {
intro_node_blinding_point: Option<PublicKey>,
keysend_preimage: Option<PaymentPreimage>,
custom_tlvs: Vec<(u64, Vec<u8>)>,
authentication: (Hmac<Sha256>, Nonce),
}
}

Expand Down Expand Up @@ -2908,7 +2912,7 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundOnionPayload wh
})
},
ChaChaPolyReadAdapter { readable: BlindedPaymentTlvs::Receive(ReceiveTlvs {
payment_secret, payment_constraints, payment_context, authentication: _,
payment_secret, payment_constraints, payment_context, authentication,
})} => {
if total_msat.unwrap_or(0) > MAX_VALUE_MSAT { return Err(DecodeError::InvalidValue) }
Ok(Self::BlindedReceive {
Expand All @@ -2921,6 +2925,7 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundOnionPayload wh
intro_node_blinding_point,
keysend_preimage,
custom_tlvs,
authentication,
})
},
}
Expand Down
10 changes: 6 additions & 4 deletions lightning/src/ln/onion_payment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,18 +135,19 @@ pub(super) fn create_recv_pending_htlc_info(
) -> Result<PendingHTLCInfo, InboundHTLCErr> {
let (
payment_data, keysend_preimage, custom_tlvs, onion_amt_msat, onion_cltv_expiry,
payment_metadata, payment_context, requires_blinded_error, has_recipient_created_payment_secret
payment_metadata, payment_context, requires_blinded_error, has_recipient_created_payment_secret,
authentication,
) = match hop_data {
msgs::InboundOnionPayload::Receive {
payment_data, keysend_preimage, custom_tlvs, sender_intended_htlc_amt_msat,
cltv_expiry_height, payment_metadata, ..
} =>
(payment_data, keysend_preimage, custom_tlvs, sender_intended_htlc_amt_msat,
cltv_expiry_height, payment_metadata, None, false, keysend_preimage.is_none()),
cltv_expiry_height, payment_metadata, None, false, keysend_preimage.is_none(), None),
msgs::InboundOnionPayload::BlindedReceive {
sender_intended_htlc_amt_msat, total_msat, cltv_expiry_height, payment_secret,
intro_node_blinding_point, payment_constraints, payment_context, keysend_preimage,
custom_tlvs
custom_tlvs, authentication,
} => {
check_blinded_payment_constraints(
sender_intended_htlc_amt_msat, cltv_expiry, &payment_constraints
Expand All @@ -161,7 +162,7 @@ pub(super) fn create_recv_pending_htlc_info(
let payment_data = msgs::FinalOnionHopData { payment_secret, total_msat };
(Some(payment_data), keysend_preimage, custom_tlvs,
sender_intended_htlc_amt_msat, cltv_expiry_height, None, Some(payment_context),
intro_node_blinding_point.is_none(), true)
intro_node_blinding_point.is_none(), true, Some(authentication))
}
msgs::InboundOnionPayload::Forward { .. } => {
return Err(InboundHTLCErr {
Expand Down Expand Up @@ -252,6 +253,7 @@ pub(super) fn create_recv_pending_htlc_info(
phantom_shared_secret,
custom_tlvs,
requires_blinded_error,
authentication,
}
} else {
return Err(InboundHTLCErr {
Expand Down

0 comments on commit 0ca5402

Please sign in to comment.