X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=src%2Fln%2Fonion_utils.rs;h=8783aa0bde0e107708122c62fb06f2dd9102bbf4;hb=02b541607bb7a8234675b1e2101c1e31d372706f;hp=85f98ffdd78b52ccea4c5c7b5db79412790f2af0;hpb=3bfea5b659ff912011d030cc4ccb1a53d7a8a7c9;p=rust-lightning diff --git a/src/ln/onion_utils.rs b/src/ln/onion_utils.rs index 85f98ffd..8783aa0b 100644 --- a/src/ln/onion_utils.rs +++ b/src/ln/onion_utils.rs @@ -1,12 +1,14 @@ -use ln::channelmanager::PaymentHash; +use ln::channelmanager::{PaymentHash, HTLCSource}; use ln::msgs; use ln::router::{Route,RouteHop}; -use util::internal_traits; +use util::{byte_utils, internal_traits}; use util::chacha20::ChaCha20; -use util::errors::APIError; -use util::ser::Writeable; +use util::errors::{self, APIError}; +use util::ser::{Readable, Writeable}; +use util::logger::{Logger, LogHolder}; use bitcoin_hashes::{Hash, HashEngine}; +use bitcoin_hashes::cmp::fixed_time_eq; use bitcoin_hashes::hmac::{Hmac, HmacEngine}; use bitcoin_hashes::sha256::Hash as Sha256; @@ -16,6 +18,8 @@ use secp256k1::ecdh::SharedSecret; use secp256k1; use std::ptr; +use std::io::Cursor; +use std::sync::Arc; pub(super) struct OnionKeys { #[cfg(test)] @@ -65,7 +69,7 @@ pub(super) fn construct_onion_keys_callback(secp_ctx: &Secp256k1, logger: &Arc, htlc_source: &HTLCSource, mut packet_decrypted: Vec) -> (Option, bool, Option) { + if let &HTLCSource::OutboundRoute { ref route, ref session_priv, ref first_hop_htlc_msat } = htlc_source { + let mut res = None; + let mut htlc_msat = *first_hop_htlc_msat; + let mut error_code_ret = None; + let mut next_route_hop_ix = 0; + let mut is_from_final_node = false; + + // Handle packed channel/node updates for passing back for the route handler + construct_onion_keys_callback(secp_ctx, route, session_priv, |shared_secret, _, _, route_hop| { + next_route_hop_ix += 1; + if res.is_some() { return; } + + let amt_to_forward = htlc_msat - route_hop.fee_msat; + htlc_msat = amt_to_forward; + + let ammag = gen_ammag_from_shared_secret(&shared_secret[..]); + + let mut decryption_tmp = Vec::with_capacity(packet_decrypted.len()); + decryption_tmp.resize(packet_decrypted.len(), 0); + let mut chacha = ChaCha20::new(&ammag, &[0u8; 8]); + chacha.process(&packet_decrypted, &mut decryption_tmp[..]); + packet_decrypted = decryption_tmp; + + is_from_final_node = route.hops.last().unwrap().pubkey == route_hop.pubkey; + + if let Ok(err_packet) = msgs::DecodedOnionErrorPacket::read(&mut Cursor::new(&packet_decrypted)) { + let um = gen_um_from_shared_secret(&shared_secret[..]); + let mut hmac = HmacEngine::::new(&um); + hmac.input(&err_packet.encode()[32..]); + + if fixed_time_eq(&Hmac::from_engine(hmac).into_inner(), &err_packet.hmac) { + if let Some(error_code_slice) = err_packet.failuremsg.get(0..2) { + const PERM: u16 = 0x4000; + const NODE: u16 = 0x2000; + const UPDATE: u16 = 0x1000; + + let error_code = byte_utils::slice_to_be16(&error_code_slice); + error_code_ret = Some(error_code); + + 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 + let payment_failed = (match error_code & 0xff { + 15|16|17|18|19 => true, + _ => false, + } && is_from_final_node) // PERM bit observed below even this error is from the intermediate nodes + || error_code == 21; // Special case error 21 as the Route object is bogus, TODO: Maybe fail the node if the CLTV was reasonable? + + let mut fail_channel_update = None; + + if error_code & NODE == NODE { + fail_channel_update = Some(msgs::HTLCFailChannelUpdate::NodeFailure { node_id: route_hop.pubkey, is_permanent: error_code & PERM == PERM }); + } + else if error_code & PERM == PERM { + fail_channel_update = if payment_failed {None} else {Some(msgs::HTLCFailChannelUpdate::ChannelClosed { + short_channel_id: route.hops[next_route_hop_ix - if next_route_hop_ix == route.hops.len() { 1 } else { 0 }].short_channel_id, + is_permanent: true, + })}; + } + else if error_code & UPDATE == UPDATE { + if let Some(update_len_slice) = err_packet.failuremsg.get(debug_field_size+2..debug_field_size+4) { + let update_len = byte_utils::slice_to_be16(&update_len_slice) as usize; + if let Some(update_slice) = err_packet.failuremsg.get(debug_field_size + 4..debug_field_size + 4 + update_len) { + if let Ok(chan_update) = msgs::ChannelUpdate::read(&mut Cursor::new(&update_slice)) { + // if channel_update should NOT have caused the failure: + // MAY treat the channel_update as invalid. + let is_chan_update_invalid = match error_code & 0xff { + 7 => false, + 11 => amt_to_forward > chan_update.contents.htlc_minimum_msat, + 12 => { + let new_fee = amt_to_forward.checked_mul(chan_update.contents.fee_proportional_millionths as u64).and_then(|prop_fee| { (prop_fee / 1000000).checked_add(chan_update.contents.fee_base_msat as u64) }); + new_fee.is_some() && route_hop.fee_msat >= new_fee.unwrap() + } + 13 => route_hop.cltv_expiry_delta as u16 >= chan_update.contents.cltv_expiry_delta, + 14 => false, // expiry_too_soon; always valid? + 20 => chan_update.contents.flags & 2 == 0, + _ => false, // unknown error code; take channel_update as valid + }; + fail_channel_update = if is_chan_update_invalid { + // This probably indicates the node which forwarded + // to the node in question corrupted something. + Some(msgs::HTLCFailChannelUpdate::ChannelClosed { + short_channel_id: route_hop.short_channel_id, + is_permanent: true, + }) + } else { + Some(msgs::HTLCFailChannelUpdate::ChannelUpdateMessage { + msg: chan_update, + }) + }; + } + } + } + if fail_channel_update.is_none() { + // They provided an UPDATE which was obviously bogus, not worth + // trying to relay through them anymore. + fail_channel_update = Some(msgs::HTLCFailChannelUpdate::NodeFailure { + node_id: route_hop.pubkey, + is_permanent: true, + }); + } + } else if !payment_failed { + // 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. + fail_channel_update = Some(msgs::HTLCFailChannelUpdate::NodeFailure { + node_id: route_hop.pubkey, + is_permanent: true, + }); + } + + // TODO: Here (and a few other places) we assume that BADONION errors + // are always "sourced" from the node previous to the one which failed + // to decode the onion. + res = Some((fail_channel_update, !(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 { + let log_holder = LogHolder { logger }; + log_warn!(log_holder, "Onion Error[{}({:#x}) {}({})] {}", title, error_code, debug_field, log_bytes!(&err_packet.failuremsg[4..4+debug_field_size]), description); + } + else { + let log_holder = LogHolder { logger }; + log_warn!(log_holder, "Onion Error[{}({:#x})] {}", title, error_code, description); + } + } else { + // Useless packet that we can't use but it passed HMAC, so it + // definitely came from the peer in question + res = Some((Some(msgs::HTLCFailChannelUpdate::NodeFailure { + node_id: route_hop.pubkey, + is_permanent: true, + }), !is_from_final_node)); + } + } + } + }).expect("Route that we sent via spontaneously grew invalid keys in the middle of it?"); + if let Some((channel_update, payment_retryable)) = res { + (channel_update, payment_retryable, error_code_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, !is_from_final_node, None) + } + } else { unreachable!(); } +} + #[cfg(test)] mod tests { use ln::channelmanager::PaymentHash; @@ -282,29 +437,29 @@ mod tests { let route = Route { hops: vec!( RouteHop { - pubkey: PublicKey::from_slice(&secp_ctx, &hex::decode("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]).unwrap(), + pubkey: PublicKey::from_slice(&hex::decode("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]).unwrap(), short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0 // Test vectors are garbage and not generateble from a RouteHop, we fill in payloads manually }, RouteHop { - pubkey: PublicKey::from_slice(&secp_ctx, &hex::decode("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c").unwrap()[..]).unwrap(), + pubkey: PublicKey::from_slice(&hex::decode("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c").unwrap()[..]).unwrap(), short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0 // Test vectors are garbage and not generateble from a RouteHop, we fill in payloads manually }, RouteHop { - pubkey: PublicKey::from_slice(&secp_ctx, &hex::decode("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007").unwrap()[..]).unwrap(), + pubkey: PublicKey::from_slice(&hex::decode("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007").unwrap()[..]).unwrap(), short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0 // Test vectors are garbage and not generateble from a RouteHop, we fill in payloads manually }, RouteHop { - pubkey: PublicKey::from_slice(&secp_ctx, &hex::decode("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991").unwrap()[..]).unwrap(), + pubkey: PublicKey::from_slice(&hex::decode("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991").unwrap()[..]).unwrap(), short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0 // Test vectors are garbage and not generateble from a RouteHop, we fill in payloads manually }, RouteHop { - pubkey: PublicKey::from_slice(&secp_ctx, &hex::decode("02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145").unwrap()[..]).unwrap(), + pubkey: PublicKey::from_slice(&hex::decode("02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145").unwrap()[..]).unwrap(), short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0 // Test vectors are garbage and not generateble from a RouteHop, we fill in payloads manually }, ), }; - let session_priv = SecretKey::from_slice(&secp_ctx, &hex::decode("4141414141414141414141414141414141414141414141414141414141414141").unwrap()[..]).unwrap(); + let session_priv = SecretKey::from_slice(&hex::decode("4141414141414141414141414141414141414141414141414141414141414141").unwrap()[..]).unwrap(); let onion_keys = super::construct_onion_keys(&secp_ctx, &route, &session_priv).unwrap(); assert_eq!(onion_keys.len(), route.hops.len());