X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Fln%2Fonion_utils.rs;h=d55077770f3baae35e2dab9c2f95fd2136071a50;hb=8866ed35330bae1af2237c1951d9c4025938aa65;hp=31f1d5064fe1fdba3dadf30b735c105a02efdf7f;hpb=9f5e574b0b0f0f87a48f79d3150e93570957965c;p=rust-lightning diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index 31f1d506..d5507777 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -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(secp_ctx: &Secp256k1, packet_pubkey: PublicKey, packet_shared_secret: &[u8; 32]) -> Result { +/// Calculates a pubkey for the next hop, such as the next hop's packet pubkey or blinding point. +pub(crate) fn next_hop_pubkey( + secp_ctx: &Secp256k1, curr_pubkey: PublicKey, shared_secret: &[u8] +) -> Result { 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 (secp_ctx: &Secp256k1, path: &Vec, session_priv: &SecretKey, mut callback: FType) -> Result<(), secp256k1::Error> { +pub(super) fn construct_onion_keys_callback( + secp_ctx: &Secp256k1, 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(secp_ctx: &Secp256k1, path: &Path, session_priv: &SecretKey) -> Result, 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 { @@ -380,15 +396,22 @@ pub(super) fn build_first_hop_failure_packet(shared_secret: &[u8], failure_type: encrypt_failure_packet(shared_secret, &failure_packet.encode()[..]) } +pub(crate) struct DecodedOnionFailure { + pub(crate) network_update: Option, + pub(crate) short_channel_id: Option, + pub(crate) payment_retryable: bool, + #[cfg(test)] + pub(crate) onion_error_code: Option, + #[cfg(test)] + pub(crate) onion_error_data: Option>, +} + /// Process failure we got back from upstream on a payment we sent (implying htlc_source is an /// OutboundRoute). -/// Returns update, a boolean indicating that the payment itself failed, the short channel id of -/// the responsible channel, and the error code. #[inline] pub(super) fn process_onion_failure( secp_ctx: &Secp256k1, logger: &L, htlc_source: &HTLCSource, mut packet_decrypted: Vec -) -> (Option, Option, bool, Option, Option>) -where L::Target: Logger { +) -> DecodedOnionFailure where L::Target: Logger { let (path, session_priv, first_hop_htlc_msat) = if let &HTLCSource::OutboundRoute { ref path, ref session_priv, ref first_hop_htlc_msat, .. } = htlc_source { @@ -400,10 +423,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 +456,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 +473,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 +484,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 +491,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 +501,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 +586,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 +616,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, @@ -605,12 +641,24 @@ where L::Target: Logger { 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((channel_update, short_channel_id, payment_retryable)) = res { - (channel_update, short_channel_id, payment_retryable, error_code_ret, error_packet_ret) + if let Some((network_update, short_channel_id, payment_retryable)) = res { + DecodedOnionFailure { + network_update, short_channel_id, payment_retryable, + #[cfg(test)] + onion_error_code: error_code_ret, + #[cfg(test)] + onion_error_data: error_packet_ret + } } else { // only not set either packet unparseable or hmac does not match with any // payment not retryable only when garbage is from the final node - (None, None, !is_from_final_node, None, None) + DecodedOnionFailure { + network_update: None, short_channel_id: None, payment_retryable: !is_from_final_node, + #[cfg(test)] + onion_error_code: None, + #[cfg(test)] + onion_error_data: None + } } } @@ -734,12 +782,12 @@ impl HTLCFailReason { pub(super) fn decode_onion_failure( &self, secp_ctx: &Secp256k1, logger: &L, htlc_source: &HTLCSource - ) -> (Option, Option, bool, Option, Option>) - where L::Target: Logger { + ) -> DecodedOnionFailure where L::Target: Logger { match self.0 { HTLCFailReasonRepr::LightningError { ref err } => { process_onion_failure(secp_ctx, logger, &htlc_source, err.data.clone()) }, + #[allow(unused)] HTLCFailReasonRepr::Reason { ref failure_code, ref data, .. } => { // we get a fail_malformed_htlc from the first hop // TODO: We'd like to generate a NetworkUpdate for temporary @@ -747,7 +795,15 @@ impl HTLCFailReason { // generally ignores its view of our own channels as we provide them via // ChannelDetails. if let &HTLCSource::OutboundRoute { ref path, .. } = htlc_source { - (None, Some(path.hops[0].short_channel_id), true, Some(*failure_code), Some(data.clone())) + DecodedOnionFailure { + network_update: None, + payment_retryable: true, + short_channel_id: Some(path.hops[0].short_channel_id), + #[cfg(test)] + onion_error_code: Some(*failure_code), + #[cfg(test)] + onion_error_data: Some(data.clone()), + } } else { unreachable!(); } } }