X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Fln%2Fonion_utils.rs;h=0dd6087f82018d9faf56d53672325b76284caa00;hb=e6024ab7881a37f2a7f6b43dd2e832ead1a1d6c6;hp=c1767c025a3531fe61e523ea39a7250eef5f9732;hpb=c0bbd4d91877b3f7eca5b6aba877257acf4eec0b;p=rust-lightning diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index c1767c02..0dd6087f 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -12,7 +12,7 @@ use ln::channelmanager::HTLCSource; use ln::msgs; use routing::network_graph::NetworkUpdate; use routing::router::RouteHop; -use util::chacha20::ChaCha20; +use util::chacha20::{ChaCha20, ChaChaReader}; use util::errors::{self, APIError}; use util::ser::{Readable, Writeable, LengthCalculatingWriter}; use util::logger::Logger; @@ -28,7 +28,7 @@ use bitcoin::secp256k1::ecdh::SharedSecret; use bitcoin::secp256k1; use prelude::*; -use io::Cursor; +use io::{Cursor, Read}; use core::convert::TryInto; use core::ops::Deref; @@ -380,7 +380,7 @@ 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, + 15|16|17|18|19|23 => true, _ => false, } && is_from_final_node; // PERM bit observed below even if this error is from the intermediate nodes @@ -506,6 +506,114 @@ pub(super) fn process_onion_failure(secp_ctx: & } 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 io; @@ -555,7 +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 }, ]], - payee: None, + payment_params: None, }; let session_priv = SecretKey::from_slice(&hex::decode("4141414141414141414141414141414141414141414141414141414141414141").unwrap()[..]).unwrap();