Merge pull request #2722 from benthecarman/dust-overflow
[rust-lightning] / lightning / src / ln / onion_utils.rs
index c591010ae4dd671a5e5ee42b4b0bb5efb61e8c44..e952bd8e2e125c5d8c16ef6f8419e1c8a2579ba0 100644 (file)
@@ -93,7 +93,7 @@ pub(super) fn gen_pad_from_shared_secret(shared_secret: &[u8]) -> [u8; 32] {
 }
 
 /// Calculates a pubkey for the next hop, such as the next hop's packet pubkey or blinding point.
-pub(crate) fn next_hop_pubkey<T: secp256k1::Signing + secp256k1::Verification>(
+pub(crate) fn next_hop_pubkey<T: secp256k1::Verification>(
        secp_ctx: &Secp256k1<T>, curr_pubkey: PublicKey, shared_secret: &[u8]
 ) -> Result<PublicKey, secp256k1::Error> {
        let blinding_factor = {
@@ -426,7 +426,7 @@ pub(super) fn build_first_hop_failure_packet(shared_secret: &[u8], failure_type:
 pub(crate) struct DecodedOnionFailure {
        pub(crate) network_update: Option<NetworkUpdate>,
        pub(crate) short_channel_id: Option<u64>,
-       pub(crate) payment_retryable: bool,
+       pub(crate) payment_failed_permanently: bool,
        #[cfg(test)]
        pub(crate) onion_error_code: Option<u16>,
        #[cfg(test)]
@@ -444,7 +444,14 @@ pub(super) fn process_onion_failure<T: secp256k1::Signing, L: Deref>(
        } = htlc_source {
                (path, session_priv, first_hop_htlc_msat)
        } else { unreachable!() };
-       let mut res = None;
+
+       // Learnings from the HTLC failure to inform future payment retries and scoring.
+       struct FailureLearnings {
+               network_update: Option<NetworkUpdate>,
+               short_channel_id: Option<u64>,
+               payment_failed_permanently: bool,
+       }
+       let mut res: Option<FailureLearnings> = None;
        let mut htlc_msat = *first_hop_htlc_msat;
        let mut error_code_ret = None;
        let mut error_packet_ret = None;
@@ -467,11 +474,33 @@ pub(super) fn process_onion_failure<T: secp256k1::Signing, L: Deref>(
                                // Got an error from within a blinded route.
                                error_code_ret = Some(BADONION | PERM | 24); // invalid_onion_blinding
                                error_packet_ret = Some(vec![0; 32]);
-                               is_from_final_node = false;
+                               res = Some(FailureLearnings {
+                                       network_update: None, short_channel_id: None, payment_failed_permanently: false
+                               });
                                return
                        },
                };
 
+               // The failing hop includes either the inbound channel to the recipient or the outbound channel
+               // from the current hop (i.e., the next hop's inbound channel).
+               let num_blinded_hops = path.blinded_tail.as_ref().map_or(0, |bt| bt.hops.len());
+               // For 1-hop blinded paths, the final `path.hops` entry is the recipient.
+               is_from_final_node = route_hop_idx + 1 == path.hops.len() && num_blinded_hops <= 1;
+               let failing_route_hop = if is_from_final_node { route_hop } else {
+                       match path.hops.get(route_hop_idx + 1) {
+                               Some(hop) => hop,
+                               None => {
+                                       // The failing hop is within a multi-hop blinded path.
+                                       error_code_ret = Some(BADONION | PERM | 24); // invalid_onion_blinding
+                                       error_packet_ret = Some(vec![0; 32]);
+                                       res = Some(FailureLearnings {
+                                               network_update: None, short_channel_id: None, payment_failed_permanently: false
+                                       });
+                                       return
+                               }
+                       }
+               };
+
                let amt_to_forward = htlc_msat - route_hop.fee_msat;
                htlc_msat = amt_to_forward;
 
@@ -483,11 +512,6 @@ pub(super) fn process_onion_failure<T: secp256k1::Signing, L: Deref>(
                chacha.process(&packet_decrypted, &mut decryption_tmp[..]);
                packet_decrypted = decryption_tmp;
 
-               // The failing hop includes either the inbound channel to the recipient or the outbound channel
-               // from the current hop (i.e., the next hop's inbound channel).
-               is_from_final_node = route_hop_idx + 1 == path.hops.len();
-               let failing_route_hop = if is_from_final_node { route_hop } else { &path.hops[route_hop_idx + 1] };
-
                let err_packet = match msgs::DecodedOnionErrorPacket::read(&mut Cursor::new(&packet_decrypted)) {
                        Ok(p) => p,
                        Err(_) => return
@@ -507,7 +531,9 @@ pub(super) fn process_onion_failure<T: secp256k1::Signing, L: Deref>(
                                        is_permanent: true,
                                });
                                let short_channel_id = Some(route_hop.short_channel_id);
-                               res = Some((network_update, short_channel_id, is_from_final_node));
+                               res = Some(FailureLearnings {
+                                       network_update, short_channel_id, payment_failed_permanently: is_from_final_node
+                               });
                                return
                        }
                };
@@ -615,8 +641,9 @@ pub(super) fn process_onion_failure<T: secp256k1::Signing, L: Deref>(
                                                        } else {
                                                                // The node in question intentionally encoded a 0-length channel update. This is
                                                                // likely due to https://github.com/ElementsProject/lightning/issues/6200.
+                                                               short_channel_id = Some(failing_route_hop.short_channel_id);
                                                                network_update = Some(NetworkUpdate::ChannelFailure {
-                                                                       short_channel_id: route_hop.short_channel_id,
+                                                                       short_channel_id: failing_route_hop.short_channel_id,
                                                                        is_permanent: false,
                                                                });
                                                        }
@@ -659,7 +686,10 @@ pub(super) fn process_onion_failure<T: secp256k1::Signing, L: Deref>(
                        short_channel_id = Some(route_hop.short_channel_id);
                }
 
-               res = Some((network_update, short_channel_id, error_code & PERM == PERM && is_from_final_node));
+               res = Some(FailureLearnings {
+                       network_update, short_channel_id,
+                       payment_failed_permanently: error_code & PERM == PERM && is_from_final_node
+               });
 
                let (description, title) = errors::get_onion_error_description(error_code);
                if debug_field_size > 0 && err_packet.failuremsg.len() >= 4 + debug_field_size {
@@ -668,9 +698,11 @@ pub(super) fn process_onion_failure<T: secp256k1::Signing, L: Deref>(
                        log_info!(logger, "Onion Error[from {}: {}({:#x})] {}", route_hop.pubkey, title, error_code, description);
                }
        }).expect("Route that we sent via spontaneously grew invalid keys in the middle of it?");
-       if let Some((network_update, short_channel_id, payment_failed_permanently)) = res {
+       if let Some(FailureLearnings {
+               network_update, short_channel_id, payment_failed_permanently
+       }) = res {
                DecodedOnionFailure {
-                       network_update, short_channel_id, payment_retryable: !payment_failed_permanently,
+                       network_update, short_channel_id, payment_failed_permanently,
                        #[cfg(test)]
                        onion_error_code: error_code_ret,
                        #[cfg(test)]
@@ -680,7 +712,7 @@ pub(super) fn process_onion_failure<T: secp256k1::Signing, L: Deref>(
                // only not set either packet unparseable or hmac does not match with any
                // payment not retryable only when garbage is from the final node
                DecodedOnionFailure {
-                       network_update: None, short_channel_id: None, payment_retryable: !is_from_final_node,
+                       network_update: None, short_channel_id: None, payment_failed_permanently: is_from_final_node,
                        #[cfg(test)]
                        onion_error_code: None,
                        #[cfg(test)]
@@ -824,7 +856,7 @@ impl HTLCFailReason {
                                if let &HTLCSource::OutboundRoute { ref path, .. } = htlc_source {
                                        DecodedOnionFailure {
                                                network_update: None,
-                                               payment_retryable: true,
+                                               payment_failed_permanently: false,
                                                short_channel_id: Some(path.hops[0].short_channel_id),
                                                #[cfg(test)]
                                                onion_error_code: Some(*failure_code),
@@ -903,6 +935,27 @@ pub(crate) fn decode_next_payment_hop<NS: Deref>(
        }
 }
 
+/// Build a payment onion, returning the first hop msat and cltv values as well.
+/// `cur_block_height` should be set to the best known block height + 1.
+pub fn create_payment_onion<T: secp256k1::Signing>(
+       secp_ctx: &Secp256k1<T>, path: &Path, session_priv: &SecretKey, total_msat: u64,
+       recipient_onion: RecipientOnionFields, cur_block_height: u32, payment_hash: &PaymentHash,
+       keysend_preimage: &Option<PaymentPreimage>, prng_seed: [u8; 32]
+) -> Result<(msgs::OnionPacket, u64, u32), APIError> {
+       let onion_keys = construct_onion_keys(&secp_ctx, &path, &session_priv)
+               .map_err(|_| APIError::InvalidRoute{
+                       err: "Pubkey along hop was maliciously selected".to_owned()
+               })?;
+       let (onion_payloads, htlc_msat, htlc_cltv) = build_onion_payloads(
+               &path, total_msat, recipient_onion, cur_block_height, keysend_preimage
+       )?;
+       let onion_packet = construct_onion_packet(onion_payloads, onion_keys, prng_seed, payment_hash)
+               .map_err(|_| APIError::InvalidRoute{
+                       err: "Route size too large considering onion data".to_owned()
+               })?;
+       Ok((onion_packet, htlc_msat, htlc_cltv))
+}
+
 pub(crate) fn decode_next_untagged_hop<T, R: ReadableArgs<T>, N: NextPacketBytes>(shared_secret: [u8; 32], hop_data: &[u8], hmac_bytes: [u8; 32], read_args: T) -> Result<(R, Option<([u8; 32], N)>), OnionDecodeErr> {
        decode_next_hop(shared_secret, hop_data, hmac_bytes, None, read_args)
 }
@@ -1014,27 +1067,27 @@ mod tests {
                                        RouteHop {
                                                pubkey: PublicKey::from_slice(&hex::decode("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]).unwrap(),
                                                channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
-                                               short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0 // We fill in the payloads manually instead of generating them from RouteHops.
+                                               short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0, maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops.
                                        },
                                        RouteHop {
                                                pubkey: PublicKey::from_slice(&hex::decode("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c").unwrap()[..]).unwrap(),
                                                channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
-                                               short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0 // We fill in the payloads manually instead of generating them from RouteHops.
+                                               short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0, maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops.
                                        },
                                        RouteHop {
                                                pubkey: PublicKey::from_slice(&hex::decode("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007").unwrap()[..]).unwrap(),
                                                channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
-                                               short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0 // We fill in the payloads manually instead of generating them from RouteHops.
+                                               short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0, maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops.
                                        },
                                        RouteHop {
                                                pubkey: PublicKey::from_slice(&hex::decode("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991").unwrap()[..]).unwrap(),
                                                channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
-                                               short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0 // We fill in the payloads manually instead of generating them from RouteHops.
+                                               short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0, maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops.
                                        },
                                        RouteHop {
                                                pubkey: PublicKey::from_slice(&hex::decode("02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145").unwrap()[..]).unwrap(),
                                                channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
-                                               short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0 // We fill in the payloads manually instead of generating them from RouteHops.
+                                               short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0, maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops.
                                        },
                        ], blinded_tail: None }],
                        route_params: None,