diff --git a/lightning-invoice/src/lib.rs b/lightning-invoice/src/lib.rs index 0923fcc1756..d98a3178261 100644 --- a/lightning-invoice/src/lib.rs +++ b/lightning-invoice/src/lib.rs @@ -519,12 +519,15 @@ impl InvoiceBuilder Self { + let mut features = InvoiceFeatures::empty(); + features.set_attributable_errors_optional(); + InvoiceBuilder { currency, amount: None, si_prefix: None, timestamp: None, - tagged_fields: Vec::new(), + tagged_fields: vec![TaggedField::Features(features)], error: None, phantom_d: core::marker::PhantomData, diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 498813de7a1..3b91ece3d32 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -49,7 +49,7 @@ use crate::routing::router::{BlindedTail, DefaultRouter, InFlightHtlcs, Path, Pa use crate::routing::scoring::ProbabilisticScorer; use crate::ln::msgs; use crate::ln::onion_utils; -use crate::ln::onion_utils::HTLCFailReason; +use crate::ln::onion_utils::{HTLCFailReason,FailureStructure}; use crate::ln::msgs::{ChannelMessageHandler, DecodeError, LightningError, MAX_VALUE_MSAT}; #[cfg(test)] use crate::ln::outbound_payment; @@ -128,6 +128,7 @@ pub(super) struct PendingHTLCInfo { /// may overshoot this in either case) pub(super) outgoing_amt_msat: u64, pub(super) outgoing_cltv_value: u32, + pub(super) structure: Option, } #[derive(Clone)] // See Channel::revoke_and_ack for why, tl;dr: Rust bug @@ -167,7 +168,7 @@ pub(super) enum HTLCForwardInfo { } /// Tracks the inbound corresponding to an outbound HTLC -#[derive(Clone, Hash, PartialEq, Eq)] +#[derive(Clone, Hash, PartialEq, Eq, Debug)] pub(crate) struct HTLCPreviousHopData { // Note that this may be an outbound SCID alias for the associated channel. short_channel_id: u64, @@ -178,6 +179,8 @@ pub(crate) struct HTLCPreviousHopData { // This field is consumed by `claim_funds_from_hop()` when updating a force-closed backwards // channel with a preimage provided by the forward channel. outpoint: OutPoint, + + structure: Option, } enum OnionPayload { @@ -278,7 +281,7 @@ impl_writeable_tlv_based_enum!(SentHTLCId, /// Tracks the inbound corresponding to an outbound HTLC #[allow(clippy::derive_hash_xor_eq)] // Our Hash is faithful to the data, we just don't have SecretKey::hash -#[derive(Clone, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq, Debug)] pub(crate) enum HTLCSource { PreviousHopData(HTLCPreviousHopData), OutboundRoute { @@ -2327,6 +2330,7 @@ where } }, }; + Ok(PendingHTLCInfo { routing, payment_hash, @@ -2334,6 +2338,7 @@ where incoming_amt_msat: Some(amt_msat), outgoing_amt_msat: hop_data.amt_to_forward, outgoing_cltv_value: hop_data.outgoing_cltv_value, + structure: hop_data.structure, }) } @@ -2373,11 +2378,15 @@ where ($msg: expr, $err_code: expr, $data: expr) => { { log_info!(self.logger, "Failed to accept/forward incoming HTLC: {}", $msg); + let no_structure = FailureStructure { // TODO: Return legacy failure. + max_hops: 3, + payload_len: 8, + }; return PendingHTLCStatus::Fail(HTLCFailureMsg::Relay(msgs::UpdateFailHTLC { channel_id: msg.channel_id, htlc_id: msg.htlc_id, reason: HTLCFailReason::reason($err_code, $data.to_vec()) - .get_encrypted_failure_packet(&shared_secret, &None), + .get_encrypted_failure_packet(&shared_secret, &None, &no_structure), })); } } @@ -2433,6 +2442,7 @@ where incoming_amt_msat: Some(msg.amount_msat), outgoing_amt_msat: next_hop_data.amt_to_forward, outgoing_cltv_value: next_hop_data.outgoing_cltv_value, + structure: next_hop_data.structure, }) } }; @@ -3219,6 +3229,7 @@ where htlc_id: payment.prev_htlc_id, incoming_packet_shared_secret: payment.forward_info.incoming_shared_secret, phantom_shared_secret: None, + structure: payment.forward_info.structure, }); let failure_reason = HTLCFailReason::from_failure_code(0x4000 | 10); @@ -3253,7 +3264,8 @@ where prev_short_channel_id, prev_htlc_id, prev_funding_outpoint, prev_user_channel_id, forward_info: PendingHTLCInfo { routing, incoming_shared_secret, payment_hash, outgoing_amt_msat, - outgoing_cltv_value, incoming_amt_msat: _ + outgoing_cltv_value, incoming_amt_msat: _, + structure, } }) => { macro_rules! failure_handler { @@ -3266,6 +3278,7 @@ where htlc_id: prev_htlc_id, incoming_packet_shared_secret: incoming_shared_secret, phantom_shared_secret: $phantom_ss, + structure, }); let reason = if $next_hop_unknown { @@ -3367,6 +3380,7 @@ where forward_info: PendingHTLCInfo { incoming_shared_secret, payment_hash, outgoing_amt_msat, outgoing_cltv_value, routing: PendingHTLCRouting::Forward { onion_packet, .. }, incoming_amt_msat: _, + structure, }, }) => { log_trace!(self.logger, "Adding HTLC from short id {} with payment_hash {} to channel with short id {} after delay", prev_short_channel_id, log_bytes!(payment_hash.0), short_chan_id); @@ -3377,6 +3391,7 @@ where incoming_packet_shared_secret: incoming_shared_secret, // Phantom payments are only PendingHTLCRouting::Receive. phantom_shared_secret: None, + structure, }); if let Err(e) = chan.get_mut().queue_add_htlc(outgoing_amt_msat, payment_hash, outgoing_cltv_value, htlc_source.clone(), @@ -3424,7 +3439,8 @@ where HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { prev_short_channel_id, prev_htlc_id, prev_funding_outpoint, prev_user_channel_id, forward_info: PendingHTLCInfo { - routing, incoming_shared_secret, payment_hash, incoming_amt_msat, outgoing_amt_msat, .. + routing, incoming_shared_secret, payment_hash, incoming_amt_msat, outgoing_amt_msat, + structure, .. } }) => { let (cltv_expiry, onion_payload, payment_data, phantom_shared_secret, mut onion_fields) = match routing { @@ -3451,6 +3467,7 @@ where htlc_id: prev_htlc_id, incoming_packet_shared_secret: incoming_shared_secret, phantom_shared_secret, + structure: structure, }, // We differentiate the received value from the sender intended value // if possible so that we don't prematurely mark MPP payments complete @@ -3479,6 +3496,7 @@ where htlc_id: $htlc.prev_hop.htlc_id, incoming_packet_shared_secret: $htlc.prev_hop.incoming_packet_shared_secret, phantom_shared_secret, + structure: $htlc.prev_hop.structure, }), payment_hash, HTLCFailReason::reason(0x4000 | 15, htlc_msat_height_data), HTLCDestination::FailedPayment { payment_hash: $payment_hash }, @@ -3676,6 +3694,7 @@ where HTLCForwardInfo::FailHTLC { .. } => { panic!("Got pending fail of our own HTLC"); } + _ => panic!("Unsupported forward info"), } } } @@ -4099,9 +4118,11 @@ where &self.pending_events, &self.logger) { self.push_pending_forwards_ev(); } }, - HTLCSource::PreviousHopData(HTLCPreviousHopData { ref short_channel_id, ref htlc_id, ref incoming_packet_shared_secret, ref phantom_shared_secret, ref outpoint }) => { + HTLCSource::PreviousHopData(HTLCPreviousHopData { ref short_channel_id, ref htlc_id, ref incoming_packet_shared_secret, ref phantom_shared_secret, ref outpoint, structure: Some(structure) }) => { log_trace!(self.logger, "Failing HTLC with payment_hash {} backwards from us with {:?}", log_bytes!(payment_hash.0), onion_error); - let err_packet = onion_error.get_encrypted_failure_packet(incoming_packet_shared_secret, phantom_shared_secret); + let err_packet = onion_error.get_encrypted_failure_packet(incoming_packet_shared_secret, phantom_shared_secret, structure); + + log_trace!(self.logger, "Failure packet length: {}", err_packet.data.len()); let mut push_forward_ev = false; let mut forward_htlcs = self.forward_htlcs.lock().unwrap(); @@ -4124,6 +4145,7 @@ where failed_next_destination: destination, }); }, + _ => panic!("Unhandled htlc source type {:?}", source), } } @@ -5034,13 +5056,13 @@ where // but if we've sent a shutdown and they haven't acknowledged it yet, we just // want to reject the new HTLC and fail it backwards instead of forwarding. match pending_forward_info { - PendingHTLCStatus::Forward(PendingHTLCInfo { ref incoming_shared_secret, .. }) => { + PendingHTLCStatus::Forward(PendingHTLCInfo { ref incoming_shared_secret, structure: Some(structure), .. }) => { // TODO: Implement legacy. let reason = if (error_code & 0x1000) != 0 { let (real_code, error_data) = self.get_htlc_inbound_temp_fail_err_and_data(error_code, chan); HTLCFailReason::reason(real_code, error_data) } else { HTLCFailReason::from_failure_code(error_code) - }.get_encrypted_failure_packet(incoming_shared_secret, &None); + }.get_encrypted_failure_packet(incoming_shared_secret, &None, &structure); let msg = msgs::UpdateFailHTLC { channel_id: msg.channel_id, htlc_id: msg.htlc_id, @@ -5190,6 +5212,7 @@ where htlc_id: prev_htlc_id, incoming_packet_shared_secret: forward_info.incoming_shared_secret, phantom_shared_secret: None, + structure: forward_info.structure, }); failed_intercept_forwards.push((htlc_source, forward_info.payment_hash, @@ -6273,6 +6296,7 @@ where incoming_packet_shared_secret: htlc.forward_info.incoming_shared_secret, phantom_shared_secret: None, outpoint: htlc.prev_funding_outpoint, + structure: htlc.forward_info.structure.clone(), }); let requested_forward_scid /* intercept scid */ = match htlc.forward_info.routing { @@ -6695,6 +6719,7 @@ pub fn provided_init_features(_config: &UserConfig) -> InitFeatures { features.set_channel_type_optional(); features.set_scid_privacy_optional(); features.set_zero_conf_optional(); + features.set_attributable_errors_optional(); #[cfg(anchors)] { // Attributes are not allowed on if expressions on our current MSRV of 1.41. if _config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx { @@ -6855,6 +6880,26 @@ impl_writeable_tlv_based_enum!(PendingHTLCRouting, }, ;); +impl Writeable for FailureStructure { + fn write(&self,w: &mut W) -> Result<(),io::Error>{ + self.max_hops.write(w)?; + self.payload_len.write(w)?; + + Ok(()) + } +} + +impl Readable for FailureStructure { + fn read(r: &mut R) -> Result { + let max_hops: u8 = Readable::read(r)?; + let payload_len: u8 = Readable::read(r)?; + Ok(FailureStructure { + max_hops, + payload_len, + }) + } +} + impl_writeable_tlv_based!(PendingHTLCInfo, { (0, routing, required), (2, incoming_shared_secret, required), @@ -6862,6 +6907,7 @@ impl_writeable_tlv_based!(PendingHTLCInfo, { (6, outgoing_amt_msat, required), (8, outgoing_cltv_value, required), (9, incoming_amt_msat, option), + (11, structure, option), }); @@ -6942,7 +6988,8 @@ impl_writeable_tlv_based!(HTLCPreviousHopData, { (1, phantom_shared_secret, option), (2, outpoint, required), (4, htlc_id, required), - (6, incoming_packet_shared_secret, required) + (6, incoming_packet_shared_secret, required), + (7, structure, option) }); impl Writeable for ClaimableHTLC { diff --git a/lightning/src/ln/features.rs b/lightning/src/ln/features.rs index cf375603b37..2e9f1c83945 100644 --- a/lightning/src/ln/features.rs +++ b/lightning/src/ln/features.rs @@ -136,7 +136,7 @@ mod sealed { // Byte 2 BasicMPP | Wumbo | AnchorsZeroFeeHtlcTx, // Byte 3 - ShutdownAnySegwit, + ShutdownAnySegwit | AttributableErrors, // Byte 4 OnionMessages, // Byte 5 @@ -152,7 +152,7 @@ mod sealed { // Byte 2 BasicMPP | Wumbo | AnchorsZeroFeeHtlcTx, // Byte 3 - ShutdownAnySegwit, + ShutdownAnySegwit | AttributableErrors, // Byte 4 OnionMessages, // Byte 5 @@ -169,7 +169,7 @@ mod sealed { // Byte 2 BasicMPP, // Byte 3 - , + AttributableErrors, // Byte 4 , // Byte 5 @@ -384,6 +384,9 @@ mod sealed { define_feature!(27, ShutdownAnySegwit, [InitContext, NodeContext], "Feature flags for `opt_shutdown_anysegwit`.", set_shutdown_any_segwit_optional, set_shutdown_any_segwit_required, supports_shutdown_anysegwit, requires_shutdown_anysegwit); + define_feature!(29, AttributableErrors, [InitContext, NodeContext, InvoiceContext], + "Feature flags for `option_attributable_errors`.", set_attributable_errors_optional, + set_attributable_errors_required, supports_attributable_errors, requires_attributable_errors); define_feature!(39, OnionMessages, [InitContext, NodeContext], "Feature flags for `option_onion_messages`.", set_onion_messages_optional, set_onion_messages_required, supports_onion_messages, requires_onion_messages); @@ -863,6 +866,7 @@ mod tests { init_features.set_basic_mpp_optional(); init_features.set_wumbo_optional(); init_features.set_shutdown_any_segwit_optional(); + init_features.set_attributable_errors_optional(); init_features.set_onion_messages_optional(); init_features.set_channel_type_optional(); init_features.set_scid_privacy_optional(); diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index 4b2eb9674fa..9ae76ed105d 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -31,7 +31,7 @@ use bitcoin::blockdata::script::Script; use bitcoin::hash_types::{Txid, BlockHash}; use crate::ln::features::{ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures}; -use crate::ln::onion_utils; +use crate::ln::onion_utils::{self, FailureStructure}; use crate::onion_message; use crate::prelude::*; @@ -1149,6 +1149,7 @@ pub trait OnionMessageHandler : OnionMessageProvider { } mod fuzzy_internal_msgs { + use crate::ln::onion_utils::FailureStructure; use crate::prelude::*; use crate::ln::{PaymentPreimage, PaymentSecret}; @@ -1179,12 +1180,14 @@ mod fuzzy_internal_msgs { /// Message serialization may panic if this value is more than 21 million Bitcoin. pub(crate) amt_to_forward: u64, pub(crate) outgoing_cltv_value: u32, + pub(crate) structure: Option, } pub struct DecodedOnionErrorPacket { - pub(crate) hmac: [u8; 32], pub(crate) failuremsg: Vec, pub(crate) pad: Vec, + pub(crate) payloads: Vec, + pub(crate) hmac: Vec, } } #[cfg(fuzzing)] @@ -1425,11 +1428,16 @@ impl_writeable_msg!(CommitmentSigned, { (2, partial_signature_with_nonce, option) }); -impl_writeable!(DecodedOnionErrorPacket, { - hmac, - failuremsg, - pad -}); +impl Writeable for DecodedOnionErrorPacket { + fn write(&self,w: &mut W) -> Result<(),io::Error>{ + self.failuremsg.write(w)?; + self.pad.write(w)?; + w.write_all(&self.payloads)?; + w.write_all(&self.hmac)?; + + Ok(()) + } +} #[cfg(not(taproot))] impl_writeable_msg!(FundingCreated, { @@ -1659,7 +1667,8 @@ impl Writeable for OnionHopData { _encode_varint_length_prefixed_tlv!(w, { (2, HighZeroBytesDroppedBigSize(self.amt_to_forward), required), (4, HighZeroBytesDroppedBigSize(self.outgoing_cltv_value), required), - (6, short_channel_id, required) + (6, short_channel_id, required), + (20, self.structure, option) }); }, OnionHopDataFormat::FinalNode { ref payment_data, ref payment_metadata, ref keysend_preimage } => { @@ -1668,6 +1677,7 @@ impl Writeable for OnionHopData { (4, HighZeroBytesDroppedBigSize(self.outgoing_cltv_value), required), (8, payment_data, option), (16, payment_metadata.as_ref().map(|m| WithoutLength(m)), option), + (20, self.structure, option), (5482373484, keysend_preimage, option) }); }, @@ -1684,12 +1694,14 @@ impl Readable for OnionHopData { let mut payment_data: Option = None; let mut payment_metadata: Option>> = None; let mut keysend_preimage: Option = None; + let mut structure: Option = None; read_tlv_fields!(r, { (2, amt, required), (4, cltv_value, required), (6, short_id, option), (8, payment_data, option), (16, payment_metadata, option), + (20, structure, option), // See https://github.com/lightning/blips/blob/master/blip-0003.md (5482373484, keysend_preimage, option) }); @@ -1716,10 +1728,12 @@ impl Readable for OnionHopData { if amt.0 > MAX_VALUE_MSAT { return Err(DecodeError::InvalidValue); } + Ok(OnionHopData { format, amt_to_forward: amt.0, outgoing_cltv_value: cltv_value.0, + structure, }) } } @@ -2869,6 +2883,7 @@ mod tests { }, amt_to_forward: 0x0badf00d01020304, outgoing_cltv_value: 0xffffffff, + structure: None, }; let encoded_value = msg.encode(); let target_value = hex::decode("1a02080badf00d010203040404ffffffff0608deadbeef1bad1dea").unwrap(); @@ -2891,6 +2906,7 @@ mod tests { }, amt_to_forward: 0x0badf00d01020304, outgoing_cltv_value: 0xffffffff, + structure: None, }; let encoded_value = msg.encode(); let target_value = hex::decode("1002080badf00d010203040404ffffffff").unwrap(); @@ -2915,6 +2931,7 @@ mod tests { }, amt_to_forward: 0x0badf00d01020304, outgoing_cltv_value: 0xffffffff, + structure: None, }; let encoded_value = msg.encode(); let target_value = hex::decode("3602080badf00d010203040404ffffffff082442424242424242424242424242424242424242424242424242424242424242421badca1f").unwrap(); @@ -3092,6 +3109,7 @@ mod tests { }, amt_to_forward: 1000, outgoing_cltv_value: 0xffffffff, + structure: None, }; let mut encoded_payload = Vec::new(); let test_bytes = vec![42u8; 1000]; diff --git a/lightning/src/ln/onion_route_tests.rs b/lightning/src/ln/onion_route_tests.rs index d70016cf4ac..af68dd0ea5b 100644 --- a/lightning/src/ln/onion_route_tests.rs +++ b/lightning/src/ln/onion_route_tests.rs @@ -344,6 +344,14 @@ fn test_onion_failure() { // positive case send_payment(&nodes[0], &vec!(&nodes[1], &nodes[2])[..], 40000); + // TEMP: Test final node failure. + run_onion_failure_test("unknown_payment_hash", 2, &nodes, &route, &payment_hash, &payment_secret, |_| {}, || { + nodes[2].node.fail_htlc_backwards(&payment_hash); + }, false, Some(PERM|15), None, None); + let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[2]); + + return; + // intermediate node failure let short_channel_id = channels[1].0.contents.short_channel_id; run_onion_failure_test("invalid_realm", 0, &nodes, &route, &payment_hash, &payment_secret, |msg| { diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index ebcf83bd90d..ca16eb56779 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -33,6 +33,17 @@ use crate::io::{Cursor, Read}; use core::convert::{AsMut, TryInto}; use core::ops::Deref; +use core::panic; +use std::io::{self, Write}; + +const DEFAULT_FAILURE_STRUCTURE: FailureStructure = FailureStructure { max_hops: 27, payload_len: 8 }; + +#[derive(Clone, Hash, PartialEq, Eq, Debug)] +pub struct FailureStructure { + pub max_hops: u8, + pub payload_len: u8, +} + pub(crate) struct OnionKeys { #[cfg(test)] pub(crate) shared_secret: SharedSecret, @@ -180,6 +191,7 @@ pub(super) fn build_onion_payloads(path: &Path, total_msat: u64, mut recipient_o }, amt_to_forward: value_msat, outgoing_cltv_value: cltv, + structure: Some(DEFAULT_FAILURE_STRUCTURE), // TODO: Use hop feature bits to determine whether this is supported. }); cur_value_msat += hop.fee_msat; if cur_value_msat >= 21000000 * 100000000 * 1000 { @@ -352,11 +364,12 @@ pub(super) fn encrypt_failure_packet(shared_secret: &[u8], raw_packet: &[u8]) -> } } -pub(super) fn build_failure_packet(shared_secret: &[u8], failure_type: u16, failure_data: &[u8]) -> msgs::DecodedOnionErrorPacket { +pub(super) fn build_failure_packet(shared_secret: &[u8], failure_type: u16, failure_data: &[u8], max_hops: u8, payload_len: u8) -> msgs::DecodedOnionErrorPacket { assert_eq!(shared_secret.len(), 32); assert!(failure_data.len() <= 256 - 2); - let um = gen_um_from_shared_secret(&shared_secret); + let max_hops = max_hops as usize; + let full_payload_len = payload_len as usize + 1; // Including the marker byte.ß let failuremsg = { let mut res = Vec::with_capacity(2 + failure_data.len()); @@ -370,22 +383,27 @@ pub(super) fn build_failure_packet(shared_secret: &[u8], failure_type: u16, fail res.resize(256 - 2 - failure_data.len(), 0); res }; - let mut packet = msgs::DecodedOnionErrorPacket { - hmac: [0; 32], - failuremsg, - pad, + + let payloads = { + let mut payloads = vec![0; max_hops * full_payload_len]; + payloads[0] = 1; // final node + payloads }; - let mut hmac = HmacEngine::::new(&um); - hmac.input(&packet.encode()[32..]); - packet.hmac = Hmac::from_engine(hmac).into_inner(); + let hmac_count = max_hops * (max_hops + 1) / 2; + let hmacs = vec![0;32*hmac_count]; - packet + msgs::DecodedOnionErrorPacket { + failuremsg: failuremsg, + pad: pad, + payloads: payloads, + hmac: hmacs, + } } #[cfg(test)] pub(super) fn build_first_hop_failure_packet(shared_secret: &[u8], failure_type: u16, failure_data: &[u8]) -> msgs::OnionErrorPacket { - let failure_packet = build_failure_packet(shared_secret, failure_type, failure_data); + let failure_packet = build_failure_packet(shared_secret, failure_type, failure_data, 3, 9); encrypt_failure_packet(shared_secret, &failure_packet.encode()[..]) } @@ -404,6 +422,8 @@ pub(super) fn process_onion_failure(secp_ctx: & // 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| { + log_debug!(logger, "Processing at pos {}", route_hop_idx); + if res.is_some() { return; } let amt_to_forward = htlc_msat - route_hop.fee_msat; @@ -422,13 +442,60 @@ pub(super) fn process_onion_failure(secp_ctx: & 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] }; - if let Ok(err_packet) = msgs::DecodedOnionErrorPacket::read(&mut Cursor::new(&packet_decrypted)) { - let um = gen_um_from_shared_secret(shared_secret.as_ref()); - let mut hmac = HmacEngine::::new(&um); - hmac.input(&err_packet.encode()[32..]); + let max_hops = DEFAULT_FAILURE_STRUCTURE.max_hops as usize; + let full_payload_len = DEFAULT_FAILURE_STRUCTURE.payload_len as usize + 1; + + let packet_len = packet_decrypted.len(); + let hmac_count = max_hops * (max_hops + 1) / 2; + + let message = &packet_decrypted[..packet_len - max_hops * full_payload_len-hmac_count*32]; + let payloads = &packet_decrypted[packet_len - max_hops * full_payload_len-hmac_count*32..packet_len-hmac_count*32]; + let hmacs = &packet_decrypted[packet_len - hmac_count*32..]; + + let um = gen_um_from_shared_secret(shared_secret.as_ref()); + let mut hmac = HmacEngine::::new(&um); - 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) { + hmac.input(&message); + hmac.input(&payloads[..(max_hops - route_hop_idx) * full_payload_len]); + write_downstream_hmacs(route_hop_idx, max_hops, hmacs, &mut hmac).unwrap(); + + let actual_hmac = &hmacs[route_hop_idx * 32..route_hop_idx*32+32]; + let expected_hmac = Hmac::from_engine(hmac).into_inner(); + + if !fixed_time_eq(&expected_hmac, actual_hmac) { + log_debug!(logger, "Invalid HMAC in onion failure packet at pos {}", route_hop_idx); + + return; + } + + log_debug!(logger, "Valid HMAC in onion failure packet at pos {}", route_hop_idx); + + match payloads[0] { + 0 => { + // Shift payloads left. + let payloads = &mut packet_decrypted[packet_len - max_hops * full_payload_len-hmac_count*32..packet_len-hmac_count*32]; + payloads.copy_within(full_payload_len.., 0); + + // Shift hmacs left. + let hmacs = &mut packet_decrypted[packet_len - hmac_count*32..]; + let mut src_idx = max_hops; + let mut dest_idx = 1; + let mut copy_len = max_hops - 1; + + for i in 0..max_hops - 1 { + hmacs.copy_within(src_idx * 32 .. (src_idx + copy_len) * 32, dest_idx * 32); + + src_idx += copy_len; + dest_idx += copy_len + 1; + copy_len -= 1; + } + } + 1 => { + // Final payload, parse failure msg. + let cursor = &mut Cursor::new(message); + let failuremsg: Vec = Readable::read(cursor).unwrap(); + + if let Some(error_code_slice) = failuremsg.get(0..2) { const BADONION: u16 = 0x8000; const PERM: u16 = 0x4000; const NODE: u16 = 0x2000; @@ -436,7 +503,7 @@ pub(super) fn process_onion_failure(secp_ctx: & 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()); + error_packet_ret = Some(failuremsg[2..].to_vec()); let (debug_field, debug_field_size) = errors::get_onion_debug_field(error_code); @@ -476,9 +543,9 @@ pub(super) fn process_onion_failure(secp_ctx: & 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) { + if let Some(update_len_slice) = failuremsg.get(debug_field_size+2..debug_field_size+4) { let update_len = u16::from_be_bytes(update_len_slice.try_into().expect("len is 2")) as usize; - if let Some(mut update_slice) = err_packet.failuremsg.get(debug_field_size + 4..debug_field_size + 4 + update_len) { + if let Some(mut update_slice) = failuremsg.get(debug_field_size + 4..debug_field_size + 4 + update_len) { // Historically, the BOLTs were unclear if the message type // bytes should be included here or not. The BOLTs have now // been updated to indicate that they *are* included, but many @@ -585,8 +652,8 @@ pub(super) fn process_onion_failure(secp_ctx: & 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 { - log_info!(logger, "Onion Error[from {}: {}({:#x}) {}({})] {}", route_hop.pubkey, title, error_code, debug_field, log_bytes!(&err_packet.failuremsg[4..4+debug_field_size]), description); + if debug_field_size > 0 && failuremsg.len() >= 4 + debug_field_size { + log_info!(logger, "Onion Error[from {}: {}({:#x}) {}({})] {}", route_hop.pubkey, title, error_code, debug_field, log_bytes!(&failuremsg[4..4+debug_field_size]), description); } else { log_info!(logger, "Onion Error[from {}: {}({:#x})] {}", route_hop.pubkey, title, error_code, description); @@ -602,7 +669,11 @@ pub(super) fn process_onion_failure(secp_ctx: & res = Some((network_update, short_channel_id, !is_from_final_node)); } } + _ => { + panic!("Got a payload type we don't know how to handle!"); + } } + }).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) @@ -713,25 +784,35 @@ impl HTLCFailReason { Self(HTLCFailReasonRepr::LightningError { err: msg.reason.clone() }) } - pub(super) fn get_encrypted_failure_packet(&self, incoming_packet_shared_secret: &[u8; 32], phantom_shared_secret: &Option<[u8; 32]>) + pub(super) fn get_encrypted_failure_packet(&self, incoming_packet_shared_secret: &[u8; 32], phantom_shared_secret: &Option<[u8; 32]>, structure: &FailureStructure) -> msgs::OnionErrorPacket { match self.0 { HTLCFailReasonRepr::Reason { ref failure_code, ref data } => { if let Some(phantom_ss) = phantom_shared_secret { - let phantom_packet = build_failure_packet(phantom_ss, *failure_code, &data[..]).encode(); + let phantom_packet = build_failure_packet(phantom_ss, *failure_code, &data[..], structure.max_hops, structure.payload_len).encode(); let encrypted_phantom_packet = encrypt_failure_packet(phantom_ss, &phantom_packet); encrypt_failure_packet(incoming_packet_shared_secret, &encrypted_phantom_packet.data[..]) } else { - let packet = build_failure_packet(incoming_packet_shared_secret, *failure_code, &data[..]).encode(); + let mut packet = build_failure_packet(incoming_packet_shared_secret, *failure_code, &data[..], structure.max_hops, structure.payload_len).encode(); + + add_hmacs(incoming_packet_shared_secret, &mut packet, structure); + + println!("Failure packet: {}", log_bytes!(&packet)); + io::stdout().flush().unwrap(); + encrypt_failure_packet(incoming_packet_shared_secret, &packet) } }, HTLCFailReasonRepr::LightningError { ref err } => { - encrypt_failure_packet(incoming_packet_shared_secret, &err.data) + let packet = process_failure_packet(&err.data, incoming_packet_shared_secret, structure); + + encrypt_failure_packet(incoming_packet_shared_secret, &packet) } } } + + pub(super) fn decode_onion_failure( &self, secp_ctx: &Secp256k1, logger: &L, htlc_source: &HTLCSource ) -> (Option, Option, bool, Option, Option>) @@ -754,6 +835,114 @@ impl HTLCFailReason { } } +fn process_failure_packet(packet: &[u8], shared_secret: &[u8], structure: &FailureStructure) -> Vec { + println!("Failure packet: {}", log_bytes!(&packet)); + + let full_payload_len = structure.payload_len as usize + 1; // Including the marker byte. + let max_hops = structure.max_hops as usize; + let hmac_count = max_hops * (max_hops + 1) / 2; + + // Create new packet. + let mut processed_packet = vec![0; packet.len()]; + + // Copy message. + let message = &packet[..packet.len() - max_hops * full_payload_len - hmac_count * 32]; + processed_packet[..packet.len() - max_hops * full_payload_len - hmac_count * 32].copy_from_slice(message); + + println!("Message: {}", log_bytes!(&message)); + + // Shift payloads right. + { + let payloads = &packet[packet.len() - max_hops * full_payload_len - hmac_count * 32..packet.len()-hmac_count*32]; + let processed_payloads = &mut processed_packet[packet.len() - max_hops * full_payload_len - hmac_count * 32..packet.len()-hmac_count*32]; + processed_payloads[full_payload_len..].copy_from_slice(&payloads[..payloads.len()-full_payload_len]); + } + + // TODO: Add this nodes payload. + + // Shift hmacs right. + { + let hmacs = &packet[packet.len() - hmac_count*32..]; + let processed_hmacs = &mut processed_packet[packet.len() - hmac_count*32..]; + + let mut src_idx = hmac_count - 2; + let mut dest_idx = hmac_count - 1; + let mut copy_len = 1; + + for i in 0..max_hops - 1 { + processed_hmacs[dest_idx * 32..(dest_idx + copy_len) * 32]. + copy_from_slice(&hmacs[src_idx * 32..(src_idx + copy_len) * 32]); + + // Break at last iteration to prevent underflow when updating indices. + if i == max_hops - 2 { + break; + } + + copy_len += 1; + src_idx -= copy_len + 1; + dest_idx -= copy_len; + } + } + + // Add this node's hmacs. + add_hmacs(&shared_secret, &mut processed_packet, structure); + + println!("Failure packet post-hmac: {}", log_bytes!(&processed_packet)); + + processed_packet +} + +// Adds the current node's hmacs for all possible positions to this packet. +fn add_hmacs(shared_secret: &[u8], packet: &mut [u8], structure: &FailureStructure) { + let full_payload_len = structure.payload_len as usize + 1; // Including the marker byte. + let max_hops = structure.max_hops as usize; + let packet_len = packet.len(); + let hmac_count = max_hops * (max_hops + 1) / 2; + + let message = &packet[..packet_len - max_hops * full_payload_len-hmac_count*32]; + let payloads = &packet[packet_len - max_hops * full_payload_len-hmac_count*32..packet_len-hmac_count*32]; + let hmacs = &packet[packet_len - hmac_count*32..]; + + let mut new_hmacs = vec![0u8; max_hops * 32]; + let um = gen_um_from_shared_secret(&shared_secret); + + for i in 0..max_hops { + let mut hmac_engine = HmacEngine::::new(&um); + + hmac_engine.input(&message); + hmac_engine.input(&payloads[..(max_hops - i) * full_payload_len]); + write_downstream_hmacs(i, max_hops, hmacs, &mut hmac_engine).unwrap(); + + let hmac = Hmac::from_engine(hmac_engine).into_inner(); + + println!("hmac {}: {}", i, log_bytes!(hmac)); + + new_hmacs[i * 32..(i + 1) * 32].copy_from_slice(&hmac); + } + + let processed_hmacs = &mut packet[packet_len - hmac_count*32..]; + processed_hmacs[..new_hmacs.len()].copy_from_slice(&new_hmacs); +} + +pub fn write_downstream_hmacs( + position: usize, + max_hops: usize, + hmacs: &[u8], + w: &mut Write, +) -> Result<(), io::Error> { + let mut hmac_idx = max_hops + position; + + for j in 0..max_hops - position - 1 { + w.write_all(&hmacs[hmac_idx * 32..(hmac_idx + 1) * 32])?; + + let block_size = max_hops - j - 1; + hmac_idx += block_size; + } + + Ok(()) +} + + /// Allows `decode_next_hop` to return the next hop packet bytes for either payments or onion /// message forwards. pub(crate) trait NextPacketBytes: AsMut<[u8]> { @@ -855,7 +1044,7 @@ fn decode_next_hop, N: NextPacketBytes>(shared_secret: [u8 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_msg: "Unable to decode our hop data - cha cha", err_code: 0x4000 | 22, }); } @@ -901,11 +1090,12 @@ fn decode_next_hop, N: NextPacketBytes>(shared_secret: [u8 #[cfg(test)] mod tests { use crate::io; + use crate::ln::onion_utils::build_failure_packet; use crate::prelude::*; use crate::ln::PaymentHash; use crate::ln::features::{ChannelFeatures, NodeFeatures}; use crate::routing::router::{Path, Route, RouteHop}; - use crate::ln::msgs; + use crate::ln::msgs::{self, UpdateFailHTLC}; use crate::util::ser::{Writeable, Writer, VecWriter}; use hex; @@ -914,6 +1104,9 @@ mod tests { use bitcoin::secp256k1::{PublicKey,SecretKey}; use super::OnionKeys; + use super::write_downstream_hmacs; + use super::HTLCFailReason; + use super::FailureStructure; fn get_test_session_key() -> SecretKey { SecretKey::from_slice(&hex::decode("4141414141414141414141414141414141414141414141414141414141414141").unwrap()[..]).unwrap() @@ -1007,6 +1200,7 @@ mod tests { }, amt_to_forward: 15000, outgoing_cltv_value: 1500, + structure: None, }), /* The second payload is represented by raw hex as it contains custom type data. Content: @@ -1032,6 +1226,7 @@ mod tests { }, amt_to_forward: 12500, outgoing_cltv_value: 1250, + structure: None, }), RawOnionHopData::new(msgs::OnionHopData { format: msgs::OnionHopDataFormat::NonFinalNode { @@ -1039,6 +1234,7 @@ mod tests { }, amt_to_forward: 10000, outgoing_cltv_value: 1000, + structure: None, }), /* The fifth payload is represented by raw hex as it contains custom type data. Content: @@ -1091,7 +1287,7 @@ mod tests { // Returning Errors test vectors from BOLT 4 let onion_keys = build_test_onion_keys(); - let onion_error = super::build_failure_packet(onion_keys[4].shared_secret.as_ref(), 0x2002, &[0; 0]); + let onion_error = super::build_failure_packet(onion_keys[4].shared_secret.as_ref(), 0x2002, &[0; 0], 3, 9); assert_eq!(onion_error.encode(), hex::decode("4c2fc8bc08510334b6833ad9c3e79cd1b52ae59dfe5c2a4b23ead50f09f7ee0b0002200200fe0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap()); let onion_packet_1 = super::encrypt_failure_packet(onion_keys[4].shared_secret.as_ref(), &onion_error.encode()[..]); @@ -1110,6 +1306,64 @@ mod tests { assert_eq!(onion_packet_5.data, hex::decode("9c5add3963fc7f6ed7f148623c84134b5647e1306419dbe2174e523fa9e2fbed3a06a19f899145610741c83ad40b7712aefaddec8c6baf7325d92ea4ca4d1df8bce517f7e54554608bf2bd8071a4f52a7a2f7ffbb1413edad81eeea5785aa9d990f2865dc23b4bc3c301a94eec4eabebca66be5cf638f693ec256aec514620cc28ee4a94bd9565bc4d4962b9d3641d4278fb319ed2b84de5b665f307a2db0f7fbb757366067d88c50f7e829138fde4f78d39b5b5802f1b92a8a820865af5cc79f9f30bc3f461c66af95d13e5e1f0381c184572a91dee1c849048a647a1158cf884064deddbf1b0b88dfe2f791428d0ba0f6fb2f04e14081f69165ae66d9297c118f0907705c9c4954a199bae0bb96fad763d690e7daa6cfda59ba7f2c8d11448b604d12d").unwrap()); } + fn generate_hash_list(values: &[u8]) -> Vec { + let mut hmacs = Vec::new(); + for i in values { + let mut hmac: [u8; 32] = [0; 32]; + hmac[0] = *i; + hmacs.extend_from_slice(&hmac); + } + + hmacs + } + + #[test] + fn test_write_downstream_hmacs() { + let mut downstream_hmacs = Vec::new(); + + let hmacs = generate_hash_list(&[11, 12, 13, 14, 21, 22, 23, 31, 32, 41]); + + let result = write_downstream_hmacs(0, 4, &hmacs, &mut downstream_hmacs); + assert!(result.is_ok()); + + let expected_hmacs = generate_hash_list(&[21, 31, 41]); + assert_eq!(downstream_hmacs, expected_hmacs); + } + + #[test] + fn test_encrypt_attributable_error() { + // let structure = AttributableErrorStructure { + // hop_count: 4, + // fixed_payload_len: 8, + // }; + + let mut shared_secret_1: [u8; 32] = [0; 32]; + shared_secret_1[..3].copy_from_slice(&[1, 2, 3]); + +// let final_payload: [u8; 8] = [1, 0, 0, 0, 0, 0, 0, 0]; + + let structure = FailureStructure{ max_hops: 3, payload_len: 8 }; + + let reason = HTLCFailReason::reason(0x4000 | 15, vec![2;12]); + let packet = reason.get_encrypted_failure_packet(&shared_secret_1, &None, &structure); + + assert_eq!(packet.data, hex::decode("3b27b01e4de8aa28b4c0be023fb989d99f056d22ee27703e526221a5445cf0c11720708fbe1e4da8bf78fdbe2c2af9d223302b227d61a731fb5e5807f26abdac7ab37ac0bcd5445234586d9dd50c9eda7b64efa950e546c470a38f22a8b677effcc83e070340d68144334a99499ab9b40ac0f26402dba41365c5f9413d678a1e2ad90ebbaeafd00155880c0073f98f7f6d6789caa3cf23b82559dae2f241e2e3787f8055b7c4b90faadb6e8226ca51d0019d75263e89a83d6e7e724d91220fa54718e1318e6da3db5de4bb25a866ccbd666099a2a130dd93dba1fdce46eb3f43c149fb22e2aab36fe0b06e4c07440a54c4f8444369b974268accbd36d7ebc00862b4c2ab7fde699da6a9293f45922fe8edcc964ee117bdcb3e6dcfabc372228304202a4c36f4288d52d883e6e1d52902c05d36a5aaadbb97a2509ba6be66a8fcd0bdae09341eeffdb9c63efb367643a3fe820e40a062f9fdb115cfba7aa7fa1642b3c3f17b04a13766b4f2b511fa79a3172157c581d6dc8239bdf604eba81bf7f07e7355b4d706ffb78270cd3ff4c0d8e7c8bac1b0608891e857714c46904e707098885003160a1c00aab60754b2f97c25ecc5fa4efa96ad0ec1048c5372195de4537247f848913b41837f90f38c7de90503f3b9be69ef7446697a7e03ea6b").unwrap()); + + let fail_msg_1 = UpdateFailHTLC{ + channel_id:[0;32], + htlc_id:0, + reason: packet, + }; + + let mut shared_secret_2: [u8; 32] = [0; 32]; + shared_secret_2[..3].copy_from_slice(&[11, 12, 13]); + + let reason = HTLCFailReason::from_msg(&fail_msg_1); + let packet = reason.get_encrypted_failure_packet(&shared_secret_2, &None, &structure); + + assert_eq!(packet.data, hex::decode("bb20b6518ef912a899b287ed87fce227275e71147fd533cad7988998db0d528cb74006582904f3af979317a9c2d334510c1cc62d5230890aace77b5563f4a1fdbfdd716266bcf38958a7ecf6c881b9ca08a5b904cfa711b8ebb47ce0385d8d57fc82959152f0d9aa01d73c5179b35a461be00df5c4cbfff270c005139fd92eb43a62d9994b3fe9d8ee428aeda6c737110ca34c73c6ceb06d5c603b3c4a5dbe2ab3311defe1cf5fad6cb34d48ca158eab76fd81b17895df2c3f7a363320a1f2653763cce30dc65212263adf59fb4993857596597ef85da13db2b60f467be41da7c11f516ebb24c2cdc71e2680d500fb69bb57f3932ba36001d7a74a7b96bfce65999efb57287ef08e59f90e08cde40dcc40c446a5a9fceddff1f3063b822b1ebb27426dfa3df2054643803934194ea8e94216b7e8aca68daaf9b659a8165ed7e69e2fd7e8a23500ae79593429a827c055893b02389dd195a3b02729cd0a952b01d0034240cef411644fe377072423b39fa12a0f6ff404d4c8601241bc5ed2ab4d1c3d8a946c30cd8443b9219442f49da1a58e29248ba133768312a94328e6bca8eb628b746d849d75924a660cc89013174034db13012e4b2bdd8e2c07b994f290a3a06d8a1464005a5c1d5725f8bff81a641ba14962c3520991ec61880e1e58").unwrap()); + } + struct RawOnionHopData { data: Vec }