Merge pull request #2981 from dunxen/2024-03-PR2419fixups
[rust-lightning] / lightning / src / ln / onion_payment.rs
index 5ea07cfab30db4325bc71415f1b3f331ca1a37dc..db8c4cd033708fd36413aaf4b8c30ec05e4075e8 100644 (file)
@@ -12,7 +12,7 @@ use crate::blinded_path;
 use crate::blinded_path::payment::{PaymentConstraints, PaymentRelay};
 use crate::chain::channelmonitor::{HTLC_FAIL_BACK_BUFFER, LATENCY_GRACE_PERIOD_BLOCKS};
 use crate::ln::PaymentHash;
-use crate::ln::channelmanager::{BlindedForward, CLTV_FAR_FAR_AWAY, HTLCFailureMsg, MIN_CLTV_EXPIRY_DELTA, PendingHTLCInfo, PendingHTLCRouting};
+use crate::ln::channelmanager::{BlindedFailure, BlindedForward, CLTV_FAR_FAR_AWAY, HTLCFailureMsg, MIN_CLTV_EXPIRY_DELTA, PendingHTLCInfo, PendingHTLCRouting};
 use crate::ln::features::BlindedHopFeatures;
 use crate::ln::msgs;
 use crate::ln::onion_utils;
@@ -20,11 +20,14 @@ use crate::ln::onion_utils::{HTLCFailReason, INVALID_ONION_BLINDING};
 use crate::sign::{NodeSigner, Recipient};
 use crate::util::logger::Logger;
 
+#[allow(unused_imports)]
 use crate::prelude::*;
+
 use core::ops::Deref;
 
 /// Invalid inbound onion payment.
-pub struct InboundOnionErr {
+#[derive(Debug)]
+pub struct InboundHTLCErr {
        /// BOLT 4 error code.
        pub err_code: u16,
        /// Data attached to this error.
@@ -33,6 +36,15 @@ pub struct InboundOnionErr {
        pub msg: &'static str,
 }
 
+fn check_blinded_payment_constraints(
+       amt_msat: u64, cltv_expiry: u32, constraints: &PaymentConstraints
+) -> Result<(), ()> {
+       if amt_msat < constraints.htlc_minimum_msat ||
+               cltv_expiry > constraints.max_cltv_expiry
+       { return Err(()) }
+       Ok(())
+}
+
 fn check_blinded_forward(
        inbound_amt_msat: u64, inbound_cltv_expiry: u32, payment_relay: &PaymentRelay,
        payment_constraints: &PaymentConstraints, features: &BlindedHopFeatures
@@ -43,9 +55,8 @@ fn check_blinded_forward(
        let outgoing_cltv_value = inbound_cltv_expiry.checked_sub(
                payment_relay.cltv_expiry_delta as u32
        ).ok_or(())?;
-       if inbound_amt_msat < payment_constraints.htlc_minimum_msat ||
-               outgoing_cltv_value > payment_constraints.max_cltv_expiry
-               { return Err(()) }
+       check_blinded_payment_constraints(inbound_amt_msat, outgoing_cltv_value, payment_constraints)?;
+
        if features.requires_unknown_bits_from(&BlindedHopFeatures::empty()) { return Err(()) }
        Ok((amt_to_forward, outgoing_cltv_value))
 }
@@ -54,7 +65,7 @@ pub(super) fn create_fwd_pending_htlc_info(
        msg: &msgs::UpdateAddHTLC, hop_data: msgs::InboundOnionPayload, hop_hmac: [u8; 32],
        new_packet_bytes: [u8; onion_utils::ONION_DATA_LEN], shared_secret: [u8; 32],
        next_packet_pubkey_opt: Option<Result<PublicKey, secp256k1::Error>>
-) -> Result<PendingHTLCInfo, InboundOnionErr> {
+) -> Result<PendingHTLCInfo, InboundHTLCErr> {
        debug_assert!(next_packet_pubkey_opt.is_some());
        let outgoing_packet = msgs::OnionPacket {
                version: 0,
@@ -64,7 +75,7 @@ pub(super) fn create_fwd_pending_htlc_info(
        };
 
        let (
-               short_channel_id, amt_to_forward, outgoing_cltv_value, inbound_blinding_point
+               short_channel_id, amt_to_forward, outgoing_cltv_value, intro_node_blinding_point
        ) = match hop_data {
                msgs::InboundOnionPayload::Forward { short_channel_id, amt_to_forward, outgoing_cltv_value } =>
                        (short_channel_id, amt_to_forward, outgoing_cltv_value, None),
@@ -76,16 +87,16 @@ pub(super) fn create_fwd_pending_htlc_info(
                        ).map_err(|()| {
                                // We should be returning malformed here if `msg.blinding_point` is set, but this is
                                // unreachable right now since we checked it in `decode_update_add_htlc_onion`.
-                               InboundOnionErr {
+                               InboundHTLCErr {
                                        msg: "Underflow calculating outbound amount or cltv value for blinded forward",
                                        err_code: INVALID_ONION_BLINDING,
                                        err_data: vec![0; 32],
                                }
                        })?;
-                       (short_channel_id, amt_to_forward, outgoing_cltv_value, Some(intro_node_blinding_point))
+                       (short_channel_id, amt_to_forward, outgoing_cltv_value, intro_node_blinding_point)
                },
                msgs::InboundOnionPayload::Receive { .. } | msgs::InboundOnionPayload::BlindedReceive { .. } =>
-                       return Err(InboundOnionErr {
+                       return Err(InboundHTLCErr {
                                msg: "Final Node OnionHopData provided for us as an intermediary node",
                                err_code: 0x4000 | 22,
                                err_data: Vec::new(),
@@ -96,7 +107,13 @@ pub(super) fn create_fwd_pending_htlc_info(
                routing: PendingHTLCRouting::Forward {
                        onion_packet: outgoing_packet,
                        short_channel_id,
-                       blinded: inbound_blinding_point.map(|bp| BlindedForward { inbound_blinding_point: bp }),
+                       blinded: intro_node_blinding_point.or(msg.blinding_point)
+                               .map(|bp| BlindedForward {
+                                       inbound_blinding_point: bp,
+                                       failure: intro_node_blinding_point
+                                               .map(|_| BlindedFailure::FromIntroductionNode)
+                                               .unwrap_or(BlindedFailure::FromBlindedNode),
+                               }),
                },
                payment_hash: msg.payment_hash,
                incoming_shared_secret: shared_secret,
@@ -111,32 +128,46 @@ pub(super) fn create_recv_pending_htlc_info(
        hop_data: msgs::InboundOnionPayload, shared_secret: [u8; 32], payment_hash: PaymentHash,
        amt_msat: u64, cltv_expiry: u32, phantom_shared_secret: Option<[u8; 32]>, allow_underpay: bool,
        counterparty_skimmed_fee_msat: Option<u64>, current_height: u32, accept_mpp_keysend: bool,
-) -> Result<PendingHTLCInfo, InboundOnionErr> {
+) -> Result<PendingHTLCInfo, InboundHTLCErr> {
        let (
-               payment_data, keysend_preimage, custom_tlvs, onion_amt_msat, outgoing_cltv_value,
-               payment_metadata, requires_blinded_error
+               payment_data, keysend_preimage, custom_tlvs, onion_amt_msat, onion_cltv_expiry,
+               payment_metadata, payment_context, requires_blinded_error
        ) = match hop_data {
                msgs::InboundOnionPayload::Receive {
-                       payment_data, keysend_preimage, custom_tlvs, amt_msat, outgoing_cltv_value, payment_metadata, ..
+                       payment_data, keysend_preimage, custom_tlvs, sender_intended_htlc_amt_msat,
+                       cltv_expiry_height, payment_metadata, ..
                } =>
-                       (payment_data, keysend_preimage, custom_tlvs, amt_msat, outgoing_cltv_value, payment_metadata,
-                        false),
+                       (payment_data, keysend_preimage, custom_tlvs, sender_intended_htlc_amt_msat,
+                        cltv_expiry_height, payment_metadata, None, false),
                msgs::InboundOnionPayload::BlindedReceive {
-                       amt_msat, total_msat, outgoing_cltv_value, payment_secret, intro_node_blinding_point, ..
+                       sender_intended_htlc_amt_msat, total_msat, cltv_expiry_height, payment_secret,
+                       intro_node_blinding_point, payment_constraints, payment_context, keysend_preimage,
+                       custom_tlvs
                } => {
+                       check_blinded_payment_constraints(
+                               sender_intended_htlc_amt_msat, cltv_expiry, &payment_constraints
+                       )
+                               .map_err(|()| {
+                                       InboundHTLCErr {
+                                               err_code: INVALID_ONION_BLINDING,
+                                               err_data: vec![0; 32],
+                                               msg: "Amount or cltv_expiry violated blinded payment constraints",
+                                       }
+                               })?;
                        let payment_data = msgs::FinalOnionHopData { payment_secret, total_msat };
-                       (Some(payment_data), None, Vec::new(), amt_msat, outgoing_cltv_value, None,
+                       (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())
                }
                msgs::InboundOnionPayload::Forward { .. } => {
-                       return Err(InboundOnionErr {
+                       return Err(InboundHTLCErr {
                                err_code: 0x4000|22,
                                err_data: Vec::new(),
                                msg: "Got non final data with an HMAC of 0",
                        })
                },
                msgs::InboundOnionPayload::BlindedForward { .. } => {
-                       return Err(InboundOnionErr {
+                       return Err(InboundHTLCErr {
                                err_code: INVALID_ONION_BLINDING,
                                err_data: vec![0; 32],
                                msg: "Got blinded non final data with an HMAC of 0",
@@ -144,8 +175,8 @@ pub(super) fn create_recv_pending_htlc_info(
                }
        };
        // final_incorrect_cltv_expiry
-       if outgoing_cltv_value > cltv_expiry {
-               return Err(InboundOnionErr {
+       if onion_cltv_expiry > cltv_expiry {
+               return Err(InboundHTLCErr {
                        msg: "Upstream node set CLTV to less than the CLTV set by the sender",
                        err_code: 18,
                        err_data: cltv_expiry.to_be_bytes().to_vec()
@@ -162,7 +193,7 @@ pub(super) fn create_recv_pending_htlc_info(
                let mut err_data = Vec::with_capacity(12);
                err_data.extend_from_slice(&amt_msat.to_be_bytes());
                err_data.extend_from_slice(&current_height.to_be_bytes());
-               return Err(InboundOnionErr {
+               return Err(InboundHTLCErr {
                        err_code: 0x4000 | 15, err_data,
                        msg: "The final CLTV expiry is too soon to handle",
                });
@@ -171,7 +202,7 @@ pub(super) fn create_recv_pending_htlc_info(
                (allow_underpay && onion_amt_msat >
                 amt_msat.saturating_add(counterparty_skimmed_fee_msat.unwrap_or(0)))
        {
-               return Err(InboundOnionErr {
+               return Err(InboundHTLCErr {
                        err_code: 19,
                        err_data: amt_msat.to_be_bytes().to_vec(),
                        msg: "Upstream node sent less than we were supposed to receive in payment",
@@ -186,14 +217,14 @@ pub(super) fn create_recv_pending_htlc_info(
                // time discrepancies due to a hash collision with X.
                let hashed_preimage = PaymentHash(Sha256::hash(&payment_preimage.0).to_byte_array());
                if hashed_preimage != payment_hash {
-                       return Err(InboundOnionErr {
+                       return Err(InboundHTLCErr {
                                err_code: 0x4000|22,
                                err_data: Vec::new(),
                                msg: "Payment preimage didn't match payment hash",
                        });
                }
                if !accept_mpp_keysend && payment_data.is_some() {
-                       return Err(InboundOnionErr {
+                       return Err(InboundHTLCErr {
                                err_code: 0x4000|22,
                                err_data: Vec::new(),
                                msg: "We don't support MPP keysend payments",
@@ -203,20 +234,22 @@ pub(super) fn create_recv_pending_htlc_info(
                        payment_data,
                        payment_preimage,
                        payment_metadata,
-                       incoming_cltv_expiry: outgoing_cltv_value,
+                       incoming_cltv_expiry: onion_cltv_expiry,
                        custom_tlvs,
+                       requires_blinded_error,
                }
        } else if let Some(data) = payment_data {
                PendingHTLCRouting::Receive {
                        payment_data: data,
                        payment_metadata,
-                       incoming_cltv_expiry: outgoing_cltv_value,
+                       payment_context,
+                       incoming_cltv_expiry: onion_cltv_expiry,
                        phantom_shared_secret,
                        custom_tlvs,
                        requires_blinded_error,
                }
        } else {
-               return Err(InboundOnionErr {
+               return Err(InboundHTLCErr {
                        err_code: 0x4000|0x2000|3,
                        err_data: Vec::new(),
                        msg: "We require payment_secrets",
@@ -228,7 +261,7 @@ pub(super) fn create_recv_pending_htlc_info(
                incoming_shared_secret: shared_secret,
                incoming_amt_msat: Some(amt_msat),
                outgoing_amt_msat: onion_amt_msat,
-               outgoing_cltv_value,
+               outgoing_cltv_value: onion_cltv_expiry,
                skimmed_fee_msat: counterparty_skimmed_fee_msat,
        })
 }
@@ -245,7 +278,7 @@ pub(super) fn create_recv_pending_htlc_info(
 pub fn peel_payment_onion<NS: Deref, L: Deref, T: secp256k1::Verification>(
        msg: &msgs::UpdateAddHTLC, node_signer: &NS, logger: &L, secp_ctx: &Secp256k1<T>,
        cur_height: u32, accept_mpp_keysend: bool, allow_skimmed_fees: bool,
-) -> Result<PendingHTLCInfo, InboundOnionErr>
+) -> Result<PendingHTLCInfo, InboundHTLCErr>
 where
        NS::Target: NodeSigner,
        L::Target: Logger,
@@ -258,7 +291,7 @@ where
                        HTLCFailureMsg::Relay(r) => (0x4000 | 22, r.reason.data),
                };
                let msg = "Failed to decode update add htlc onion";
-               InboundOnionErr { msg, err_code, err_data }
+               InboundHTLCErr { msg, err_code, err_data }
        })?;
        Ok(match hop {
                onion_utils::Hop::Forward { next_hop_data, next_hop_hmac, new_packet_bytes } => {
@@ -267,7 +300,7 @@ where
                        } = match next_packet_details_opt {
                                Some(next_packet_details) => next_packet_details,
                                // Forward should always include the next hop details
-                               None => return Err(InboundOnionErr {
+                               None => return Err(InboundHTLCErr {
                                        msg: "Failed to decode update add htlc onion",
                                        err_code: 0x4000 | 22,
                                        err_data: Vec::new(),
@@ -277,7 +310,7 @@ where
                        if let Err((err_msg, code)) = check_incoming_htlc_cltv(
                                cur_height, outgoing_cltv_value, msg.cltv_expiry
                        ) {
-                               return Err(InboundOnionErr {
+                               return Err(InboundHTLCErr {
                                        msg: err_msg,
                                        err_code: code,
                                        err_data: Vec::new(),
@@ -319,11 +352,16 @@ where
                ($msg: expr, $err_code: expr) => {
                        {
                                log_info!(logger, "Failed to accept/forward incoming HTLC: {}", $msg);
+                               let (sha256_of_onion, failure_code) = if msg.blinding_point.is_some() {
+                                       ([0; 32], INVALID_ONION_BLINDING)
+                               } else {
+                                       (Sha256::hash(&msg.onion_routing_packet.hop_data).to_byte_array(), $err_code)
+                               };
                                return Err(HTLCFailureMsg::Malformed(msgs::UpdateFailMalformedHTLC {
                                        channel_id: msg.channel_id,
                                        htlc_id: msg.htlc_id,
-                                       sha256_of_onion: Sha256::hash(&msg.onion_routing_packet.hop_data).to_byte_array(),
-                                       failure_code: $err_code,
+                                       sha256_of_onion,
+                                       failure_code,
                                }));
                        }
                }
@@ -355,6 +393,10 @@ where
        macro_rules! return_err {
                ($msg: expr, $err_code: expr, $data: expr) => {
                        {
+                               if msg.blinding_point.is_some() {
+                                       return_malformed_err!($msg, INVALID_ONION_BLINDING)
+                               }
+
                                log_info!(logger, "Failed to accept/forward incoming HTLC: {}", $msg);
                                return Err(HTLCFailureMsg::Relay(msgs::UpdateFailHTLC {
                                        channel_id: msg.channel_id,
@@ -461,7 +503,7 @@ pub(super) fn check_incoming_htlc_cltv(
 mod tests {
        use bitcoin::hashes::Hash;
        use bitcoin::hashes::sha256::Hash as Sha256;
-       use bitcoin::secp256k1::{PublicKey, SecretKey};
+       use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
        use crate::ln::{PaymentPreimage, PaymentHash, PaymentSecret};
        use crate::ln::ChannelId;
        use crate::ln::channelmanager::RecipientOnionFields;
@@ -471,6 +513,38 @@ mod tests {
        use crate::routing::router::{Path, RouteHop};
        use crate::util::test_utils;
 
+       #[test]
+       fn fail_construct_onion_on_too_big_payloads() {
+               // Ensure that if we call `construct_onion_packet` and friends where payloads are too large for
+               // the allotted packet length, we'll fail to construct. Previously, senders would happily
+               // construct invalid packets by array-shifting the final node's HMAC out of the packet when
+               // adding an intermediate onion layer, causing the receiver to error with "final payload
+               // provided for us as an intermediate node."
+               let secp_ctx = Secp256k1::new();
+               let bob = crate::sign::KeysManager::new(&[2; 32], 42, 42);
+               let bob_pk = PublicKey::from_secret_key(&secp_ctx, &bob.get_node_secret_key());
+               let charlie = crate::sign::KeysManager::new(&[3; 32], 42, 42);
+               let charlie_pk = PublicKey::from_secret_key(&secp_ctx, &charlie.get_node_secret_key());
+
+               let (
+                       session_priv, total_amt_msat, cur_height, mut recipient_onion, keysend_preimage, payment_hash,
+                       prng_seed, hops, ..
+               ) = payment_onion_args(bob_pk, charlie_pk);
+
+               // Ensure the onion will not fit all the payloads by adding a large custom TLV.
+               recipient_onion.custom_tlvs.push((13377331, vec![0; 1156]));
+
+               let path = Path { hops, blinded_tail: None, };
+               let onion_keys = super::onion_utils::construct_onion_keys(&secp_ctx, &path, &session_priv).unwrap();
+               let (onion_payloads, ..) = super::onion_utils::build_onion_payloads(
+                       &path, total_amt_msat, recipient_onion, cur_height + 1, &Some(keysend_preimage)
+               ).unwrap();
+
+               assert!(super::onion_utils::construct_onion_packet(
+                               onion_payloads, onion_keys, prng_seed, &payment_hash
+               ).is_err());
+       }
+
        #[test]
        fn test_peel_payment_onion() {
                use super::*;