Document and test 0-len channel update onion error case
[rust-lightning] / lightning / src / ln / onion_utils.rs
index 31f1d5064fe1fdba3dadf30b735c105a02efdf7f..64a1e3fa3487da78dc4918d17f56b57f8c99b085 100644 (file)
@@ -91,25 +91,39 @@ pub(super) fn gen_pad_from_shared_secret(shared_secret: &[u8]) -> [u8; 32] {
        Hmac::from_engine(hmac).into_inner()
 }
 
-pub(crate) fn next_hop_packet_pubkey<T: secp256k1::Signing + secp256k1::Verification>(secp_ctx: &Secp256k1<T>, packet_pubkey: PublicKey, packet_shared_secret: &[u8; 32]) -> Result<PublicKey, secp256k1::Error> {
+/// 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>(
+       secp_ctx: &Secp256k1<T>, curr_pubkey: PublicKey, shared_secret: &[u8]
+) -> Result<PublicKey, secp256k1::Error> {
        let blinding_factor = {
                let mut sha = Sha256::engine();
-               sha.input(&packet_pubkey.serialize()[..]);
-               sha.input(packet_shared_secret);
+               sha.input(&curr_pubkey.serialize()[..]);
+               sha.input(shared_secret);
                Sha256::from_engine(sha).into_inner()
        };
 
-       packet_pubkey.mul_tweak(secp_ctx, &Scalar::from_be_bytes(blinding_factor).unwrap())
+       curr_pubkey.mul_tweak(secp_ctx, &Scalar::from_be_bytes(blinding_factor).unwrap())
 }
 
 // can only fail if an intermediary hop has an invalid public key or session_priv is invalid
 #[inline]
-pub(super) fn construct_onion_keys_callback<T: secp256k1::Signing, FType: FnMut(SharedSecret, [u8; 32], PublicKey, &RouteHop, usize)> (secp_ctx: &Secp256k1<T>, path: &Vec<RouteHop>, session_priv: &SecretKey, mut callback: FType) -> Result<(), secp256k1::Error> {
+pub(super) fn construct_onion_keys_callback<T, FType>(
+       secp_ctx: &Secp256k1<T>, path: &Path, session_priv: &SecretKey, mut callback: FType
+) -> Result<(), secp256k1::Error>
+where
+       T: secp256k1::Signing,
+       FType: FnMut(SharedSecret, [u8; 32], PublicKey, Option<&RouteHop>, usize)
+{
        let mut blinded_priv = session_priv.clone();
        let mut blinded_pub = PublicKey::from_secret_key(secp_ctx, &blinded_priv);
 
-       for (idx, hop) in path.iter().enumerate() {
-               let shared_secret = SharedSecret::new(&hop.pubkey, &blinded_priv);
+       let unblinded_hops_iter = path.hops.iter().map(|h| (&h.pubkey, Some(h)));
+       let blinded_pks_iter = path.blinded_tail.as_ref()
+               .map(|t| t.hops.iter()).unwrap_or([].iter())
+               .skip(1) // Skip the intro node because it's included in the unblinded hops
+               .map(|h| (&h.blinded_node_id, None));
+       for (idx, (pubkey, route_hop_opt)) in unblinded_hops_iter.chain(blinded_pks_iter).enumerate() {
+               let shared_secret = SharedSecret::new(pubkey, &blinded_priv);
 
                let mut sha = Sha256::engine();
                sha.input(&blinded_pub.serialize()[..]);
@@ -121,7 +135,7 @@ pub(super) fn construct_onion_keys_callback<T: secp256k1::Signing, FType: FnMut(
                blinded_priv = blinded_priv.mul_tweak(&Scalar::from_be_bytes(blinding_factor).unwrap())?;
                blinded_pub = PublicKey::from_secret_key(secp_ctx, &blinded_priv);
 
-               callback(shared_secret, blinding_factor, ephemeral_pubkey, hop, idx);
+               callback(shared_secret, blinding_factor, ephemeral_pubkey, route_hop_opt, idx);
        }
 
        Ok(())
@@ -131,7 +145,9 @@ pub(super) fn construct_onion_keys_callback<T: secp256k1::Signing, FType: FnMut(
 pub(super) fn construct_onion_keys<T: secp256k1::Signing>(secp_ctx: &Secp256k1<T>, path: &Path, session_priv: &SecretKey) -> Result<Vec<OnionKeys>, secp256k1::Error> {
        let mut res = Vec::with_capacity(path.hops.len());
 
-       construct_onion_keys_callback(secp_ctx, &path.hops, session_priv, |shared_secret, _blinding_factor, ephemeral_pubkey, _, _| {
+       construct_onion_keys_callback(secp_ctx, &path, session_priv,
+               |shared_secret, _blinding_factor, ephemeral_pubkey, _, _|
+       {
                let (rho, mu) = gen_rho_mu_from_shared_secret(shared_secret.as_ref());
 
                res.push(OnionKeys {
@@ -400,10 +416,28 @@ where L::Target: Logger {
        let mut error_packet_ret = None;
        let mut is_from_final_node = false;
 
+       const BADONION: u16 = 0x8000;
+       const PERM: u16 = 0x4000;
+       const NODE: u16 = 0x2000;
+       const UPDATE: u16 = 0x1000;
+
        // Handle packed channel/node updates for passing back for the route handler
-       construct_onion_keys_callback(secp_ctx, &path.hops, session_priv, |shared_secret, _, _, route_hop, route_hop_idx| {
+       construct_onion_keys_callback(secp_ctx, &path, session_priv,
+               |shared_secret, _, _, route_hop_opt, route_hop_idx|
+       {
                if res.is_some() { return; }
 
+               let route_hop = match route_hop_opt {
+                       Some(hop) => hop,
+                       None => {
+                               // 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;
+                               return
+                       },
+               };
+
                let amt_to_forward = htlc_msat - route_hop.fee_msat;
                htlc_msat = amt_to_forward;
 
@@ -415,8 +449,8 @@ where L::Target: Logger {
                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).
+               // 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] };
 
@@ -432,8 +466,8 @@ where L::Target: Logger {
                let error_code_slice = match err_packet.failuremsg.get(0..2) {
                        Some(s) => s,
                        None => {
-                               // Useless packet that we can't use but it passed HMAC, so it
-                               // definitely came from the peer in question
+                               // Useless packet that we can't use but it passed HMAC, so it definitely came from the peer
+                               // in question
                                let network_update = Some(NetworkUpdate::NodeFailure {
                                        node_id: route_hop.pubkey,
                                        is_permanent: true,
@@ -443,10 +477,6 @@ where L::Target: Logger {
                                return
                        }
                };
-               const BADONION: u16 = 0x8000;
-               const PERM: u16 = 0x4000;
-               const NODE: u16 = 0x2000;
-               const UPDATE: u16 = 0x1000;
 
                let error_code = u16::from_be_bytes(error_code_slice.try_into().expect("len is 2"));
                error_code_ret = Some(error_code);
@@ -454,8 +484,7 @@ where L::Target: Logger {
 
                let (debug_field, debug_field_size) = errors::get_onion_debug_field(error_code);
 
-               // indicate that payment parameter has failed and no need to
-               // update Route object
+               // indicate that payment parameter has failed and no need to update Route object
                let payment_failed = match error_code & 0xff {
                        15|16|17|18|19|23 => true,
                        _ => false,
@@ -465,14 +494,13 @@ where L::Target: Logger {
                let mut short_channel_id = None;
 
                if error_code & BADONION == BADONION {
-                       // If the error code has the BADONION bit set, always blame the channel
-                       // from the node "originating" the error to its next hop. The
-                       // "originator" is ultimately actually claiming that its counterparty
-                       // is the one who is failing the HTLC.
-                       // If the "originator" here isn't lying we should really mark the
-                       // next-hop node as failed entirely, but we can't be confident in that,
-                       // as it would allow any node to get us to completely ban one of its
-                       // counterparties. Instead, we simply remove the channel in question.
+                       // If the error code has the BADONION bit set, always blame the channel from the node
+                       // "originating" the error to its next hop. The "originator" is ultimately actually claiming
+                       // that its counterparty is the one who is failing the HTLC.
+                       // If the "originator" here isn't lying we should really mark the next-hop node as failed
+                       // entirely, but we can't be confident in that, as it would allow any node to get us to
+                       // completely ban one of its counterparties. Instead, we simply remove the channel in
+                       // question.
                        network_update = Some(NetworkUpdate::ChannelFailure {
                                short_channel_id: failing_route_hop.short_channel_id,
                                is_permanent: true,
@@ -551,6 +579,8 @@ where L::Target: Logger {
                                                                        msg: chan_update,
                                                                })
                                                        } else {
+                                                               // The node in question intentionally encoded a 0-length channel update. This is
+                                                               // likely due to https://github.com/ElementsProject/lightning/issues/6200.
                                                                network_update = Some(NetworkUpdate::ChannelFailure {
                                                                        short_channel_id: route_hop.short_channel_id,
                                                                        is_permanent: false,
@@ -579,16 +609,15 @@ where L::Target: Logger {
                                short_channel_id = Some(route_hop.short_channel_id);
                        }
                } else if payment_failed {
-                       // Only blame the hop when a value in the HTLC doesn't match the
-                       // corresponding value in the onion.
+                       // Only blame the hop when a value in the HTLC doesn't match the corresponding value in the
+                       // onion.
                        short_channel_id = match error_code & 0xff {
                                18|19 => Some(route_hop.short_channel_id),
                                _ => None,
                        };
                } else {
-                       // We can't understand their error messages and they failed to
-                       // forward...they probably can't understand our forwards so its
-                       // really not worth trying any further.
+                       // We can't understand their error messages and they failed to forward...they probably can't
+                       // understand our forwards so it's really not worth trying any further.
                        network_update = Some(NetworkUpdate::NodeFailure {
                                node_id: route_hop.pubkey,
                                is_permanent: true,