X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Fln%2Fonion_utils.rs;h=0dd6087f82018d9faf56d53672325b76284caa00;hb=952cee4a168d266d7bdd43d21ac71a3e3de46a53;hp=8feef1697af40ad33032afe23b7bf5f77e8a6b21;hpb=ba3ef0a3ba74f0347298c68fcf9e3a38ff8199e5;p=rust-lightning diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index 8feef169..0dd6087f 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -7,11 +7,12 @@ // You may not use this file except in accordance with one or both of these // licenses. -use ln::channelmanager::{PaymentHash, PaymentSecret, HTLCSource}; +use ln::{PaymentHash, PaymentPreimage, PaymentSecret}; +use ln::channelmanager::HTLCSource; use ln::msgs; +use routing::network_graph::NetworkUpdate; use routing::router::RouteHop; -use util::byte_utils; -use util::chacha20::ChaCha20; +use util::chacha20::{ChaCha20, ChaChaReader}; use util::errors::{self, APIError}; use util::ser::{Readable, Writeable, LengthCalculatingWriter}; use util::logger::Logger; @@ -26,8 +27,10 @@ use bitcoin::secp256k1::Secp256k1; use bitcoin::secp256k1::ecdh::SharedSecret; use bitcoin::secp256k1; -use std::io::Cursor; -use std::ops::Deref; +use prelude::*; +use io::{Cursor, Read}; +use core::convert::TryInto; +use core::ops::Deref; pub(super) struct OnionKeys { #[cfg(test)] @@ -72,11 +75,11 @@ pub(super) fn gen_ammag_from_shared_secret(shared_secret: &[u8]) -> [u8; 32] { // 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: &Vec, session_priv: &SecretKey, mut callback: FType) -> Result<(), secp256k1::Error> { let mut blinded_priv = session_priv.clone(); let mut blinded_pub = PublicKey::from_secret_key(secp_ctx, &blinded_priv); - for hop in path.iter() { + for (idx, hop) in path.iter().enumerate() { let shared_secret = SharedSecret::new(&hop.pubkey, &blinded_priv); let mut sha = Sha256::engine(); @@ -89,7 +92,7 @@ pub(super) fn construct_onion_keys_callback(secp_ctx: &Secp256k1, path: &Vec, session_priv: &SecretKey) -> Result, secp256k1::Error> { let mut res = Vec::with_capacity(path.len()); - construct_onion_keys_callback(secp_ctx, path, 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[..]); res.push(OnionKeys { @@ -117,7 +120,7 @@ pub(super) fn construct_onion_keys(secp_ctx: &Secp256k1, total_msat: u64, payment_secret_option: &Option, starting_htlc_offset: u32) -> Result<(Vec, u64, u32), APIError> { +pub(super) fn build_onion_payloads(path: &Vec, total_msat: u64, payment_secret_option: &Option, starting_htlc_offset: u32, keysend_preimage: &Option) -> Result<(Vec, u64, u32), APIError> { let mut cur_value_msat = 0u64; let mut cur_cltv = starting_htlc_offset; let mut last_short_channel_id = 0; @@ -139,6 +142,7 @@ pub(super) fn build_onion_payloads(path: &Vec, total_msat: u64, paymen total_msat, }) } else { None }, + keysend_preimage: *keysend_preimage, } } else { msgs::OnionHopDataFormat::NonFinalNode { @@ -325,20 +329,19 @@ pub(super) fn build_first_hop_failure_packet(shared_secret: &[u8], failure_type: /// 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, and the error code. +/// 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, bool, Option, Option>) where L::Target: Logger { - if let &HTLCSource::OutboundRoute { ref path, ref session_priv, ref first_hop_htlc_msat } = htlc_source { +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 { + if let &HTLCSource::OutboundRoute { ref path, 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 error_packet_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, path, session_priv, |shared_secret, _, _, route_hop| { - next_route_hop_ix += 1; + construct_onion_keys_callback(secp_ctx, path, session_priv, |shared_secret, _, _, route_hop, route_hop_idx| { if res.is_some() { return; } let amt_to_forward = htlc_msat - route_hop.fee_msat; @@ -352,7 +355,10 @@ pub(super) fn process_onion_failure(secp_ctx: & chacha.process(&packet_decrypted, &mut decryption_tmp[..]); packet_decrypted = decryption_tmp; - is_from_final_node = path.last().unwrap().pubkey == route_hop.pubkey; + // 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.len(); + let failing_route_hop = if is_from_final_node { route_hop } else { &path[route_hop_idx + 1] }; if let Ok(err_packet) = msgs::DecodedOnionErrorPacket::read(&mut Cursor::new(&packet_decrypted)) { let um = gen_um_from_shared_secret(&shared_secret[..]); @@ -365,7 +371,7 @@ pub(super) fn process_onion_failure(secp_ctx: & const NODE: u16 = 0x2000; const UPDATE: u16 = 0x1000; - let error_code = byte_utils::slice_to_be16(&error_code_slice); + let error_code = u16::from_be_bytes(error_code_slice.try_into().expect("len is 2")); error_code_ret = Some(error_code); error_packet_ret = Some(err_packet.failuremsg[2..].to_vec()); @@ -373,26 +379,31 @@ pub(super) fn process_onion_failure(secp_ctx: & // 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, + let payment_failed = match error_code & 0xff { + 15|16|17|18|19|23 => 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? + } && is_from_final_node; // PERM bit observed below even if this error is from the intermediate nodes - let mut fail_channel_update = None; + let mut network_update = None; + let mut short_channel_id = None; if error_code & NODE == NODE { - fail_channel_update = Some(msgs::HTLCFailChannelUpdate::NodeFailure { node_id: route_hop.pubkey, is_permanent: error_code & PERM == PERM }); + let is_permanent = error_code & PERM == PERM; + network_update = Some(NetworkUpdate::NodeFailure { node_id: route_hop.pubkey, is_permanent }); + short_channel_id = Some(route_hop.short_channel_id); } else if error_code & PERM == PERM { - fail_channel_update = if payment_failed {None} else {Some(msgs::HTLCFailChannelUpdate::ChannelClosed { - short_channel_id: path[next_route_hop_ix - if next_route_hop_ix == path.len() { 1 } else { 0 }].short_channel_id, - is_permanent: true, - })}; + if !payment_failed { + network_update = Some(NetworkUpdate::ChannelClosed { + short_channel_id: failing_route_hop.short_channel_id, + is_permanent: true, + }); + short_channel_id = Some(failing_route_hop.short_channel_id); + } } 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; + let update_len = u16::from_be_bytes(update_len_slice.try_into().expect("len is 2")) 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: @@ -400,52 +411,70 @@ pub(super) fn process_onion_failure(secp_ctx: & 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() - } + 12 => amt_to_forward + .checked_mul(chan_update.contents.fee_proportional_millionths as u64) + .map(|prop_fee| prop_fee / 1_000_000) + .and_then(|prop_fee| prop_fee.checked_add(chan_update.contents.fee_base_msat as u64)) + .map(|fee_msats| route_hop.fee_msat >= fee_msats) + .unwrap_or(false), 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 { + if is_chan_update_invalid { // This probably indicates the node which forwarded // to the node in question corrupted something. - Some(msgs::HTLCFailChannelUpdate::ChannelClosed { + network_update = Some(NetworkUpdate::ChannelClosed { short_channel_id: route_hop.short_channel_id, is_permanent: true, - }) + }); } else { - Some(msgs::HTLCFailChannelUpdate::ChannelUpdateMessage { + // Make sure the ChannelUpdate contains the expected + // short channel id. + if failing_route_hop.short_channel_id == chan_update.contents.short_channel_id { + short_channel_id = Some(failing_route_hop.short_channel_id); + } + network_update = Some(NetworkUpdate::ChannelUpdateMessage { msg: chan_update, }) }; } } } - if fail_channel_update.is_none() { + if network_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 { + network_update = Some(NetworkUpdate::NodeFailure { node_id: route_hop.pubkey, is_permanent: true, }); } - } else if !payment_failed { + if short_channel_id.is_none() { + 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. + 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. - fail_channel_update = Some(msgs::HTLCFailChannelUpdate::NodeFailure { + network_update = Some(NetworkUpdate::NodeFailure { node_id: route_hop.pubkey, is_permanent: true, }); + short_channel_id = Some(route_hop.short_channel_id); } // 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))); + res = Some((network_update, short_channel_id, !(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 { @@ -457,27 +486,139 @@ pub(super) fn process_onion_failure(secp_ctx: & } 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 { + let network_update = Some(NetworkUpdate::NodeFailure { node_id: route_hop.pubkey, is_permanent: true, - }), !is_from_final_node)); + }); + let short_channel_id = Some(route_hop.short_channel_id); + res = Some((network_update, short_channel_id, !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, error_packet_ret) + if let Some((channel_update, short_channel_id, payment_retryable)) = res { + (channel_update, short_channel_id, payment_retryable, error_code_ret, 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, !is_from_final_node, None, None) + (None, None, !is_from_final_node, None, None) } } else { unreachable!(); } } +/// Data decrypted from the onion payload. +pub(crate) enum Hop { + /// This onion payload was for us, not for forwarding to a next-hop. Contains information for + /// verifying the incoming payment. + Receive(msgs::OnionHopData), + /// This onion payload needs to be forwarded to a next-hop. + Forward { + /// Onion payload data used in forwarding the payment. + next_hop_data: msgs::OnionHopData, + /// HMAC of the next hop's onion packet. + next_hop_hmac: [u8; 32], + /// Bytes of the onion packet we're forwarding. + new_packet_bytes: [u8; 20*65], + }, +} + +/// Error returned when we fail to decode the onion packet. +pub(crate) enum OnionDecodeErr { + /// The HMAC of the onion packet did not match the hop data. + Malformed { + err_msg: &'static str, + err_code: u16, + }, + /// We failed to decode the onion payload. + Relay { + err_msg: &'static str, + err_code: u16, + }, +} + +pub(crate) fn decode_next_hop(shared_secret: [u8; 32], hop_data: &[u8], hmac_bytes: [u8; 32], payment_hash: PaymentHash) -> Result { + let (rho, mu) = gen_rho_mu_from_shared_secret(&shared_secret); + let mut hmac = HmacEngine::::new(&mu); + hmac.input(hop_data); + hmac.input(&payment_hash.0[..]); + if !fixed_time_eq(&Hmac::from_engine(hmac).into_inner(), &hmac_bytes) { + return Err(OnionDecodeErr::Malformed { + err_msg: "HMAC Check failed", + err_code: 0x8000 | 0x4000 | 5, + }); + } + + let mut chacha = ChaCha20::new(&rho, &[0u8; 8]); + let mut chacha_stream = ChaChaReader { chacha: &mut chacha, read: Cursor::new(&hop_data[..]) }; + match ::read(&mut chacha_stream) { + Err(err) => { + let error_code = match err { + msgs::DecodeError::UnknownVersion => 0x4000 | 1, // unknown realm byte + msgs::DecodeError::UnknownRequiredFeature| + msgs::DecodeError::InvalidValue| + msgs::DecodeError::ShortRead => 0x4000 | 22, // invalid_onion_payload + _ => 0x2000 | 2, // Should never happen + }; + return Err(OnionDecodeErr::Relay { + err_msg: "Unable to decode our hop data", + err_code: error_code, + }); + }, + Ok(msg) => { + let mut hmac = [0; 32]; + if let Err(_) = chacha_stream.read_exact(&mut hmac[..]) { + return Err(OnionDecodeErr::Relay { + err_msg: "Unable to decode our hop data", + err_code: 0x4000 | 22, + }); + } + if hmac == [0; 32] { + #[cfg(test)] + { + // In tests, make sure that the initial onion packet data is, at least, non-0. + // We could do some fancy randomness test here, but, ehh, whatever. + // This checks for the issue where you can calculate the path length given the + // onion data as all the path entries that the originator sent will be here + // as-is (and were originally 0s). + // Of course reverse path calculation is still pretty easy given naive routing + // algorithms, but this fixes the most-obvious case. + let mut next_bytes = [0; 32]; + chacha_stream.read_exact(&mut next_bytes).unwrap(); + assert_ne!(next_bytes[..], [0; 32][..]); + chacha_stream.read_exact(&mut next_bytes).unwrap(); + assert_ne!(next_bytes[..], [0; 32][..]); + } + return Ok(Hop::Receive(msg)); + } else { + let mut new_packet_bytes = [0; 20*65]; + let read_pos = chacha_stream.read(&mut new_packet_bytes).unwrap(); + #[cfg(debug_assertions)] + { + // Check two things: + // a) that the behavior of our stream here will return Ok(0) even if the TLV + // read above emptied out our buffer and the unwrap() wont needlessly panic + // b) that we didn't somehow magically end up with extra data. + let mut t = [0; 1]; + debug_assert!(chacha_stream.read(&mut t).unwrap() == 0); + } + // Once we've emptied the set of bytes our peer gave us, encrypt 0 bytes until we + // fill the onion hop data we'll forward to our next-hop peer. + chacha_stream.chacha.process_in_place(&mut new_packet_bytes[read_pos..]); + return Ok(Hop::Forward { + next_hop_data: msg, + next_hop_hmac: hmac, + new_packet_bytes, + }) + } + }, + } +} + #[cfg(test)] mod tests { - use ln::channelmanager::PaymentHash; + use io; + use prelude::*; + use ln::PaymentHash; use ln::features::{ChannelFeatures, NodeFeatures}; use routing::router::{Route, RouteHop}; use ln::msgs; @@ -522,6 +663,7 @@ mod tests { 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 }, ]], + payment_params: None, }; let session_priv = SecretKey::from_slice(&hex::decode("4141414141414141414141414141414141414141414141414141414141414141").unwrap()[..]).unwrap(); @@ -644,7 +786,7 @@ mod tests { } } impl Writeable for RawOnionHopData { - fn write(&self, writer: &mut W) -> Result<(), ::std::io::Error> { + fn write(&self, writer: &mut W) -> Result<(), io::Error> { writer.write_all(&self.data[..]) } }