From ea0f099ddb5dc74a441a34b821d315c19b1315ef Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Wed, 5 Mar 2025 17:58:22 +0100 Subject: [PATCH 1/2] Refactor onion_utils to encrypt/decrypt OnionErrorPacket types Prepares for extending OnionErrorPacket with attribution data. --- lightning/src/ln/onion_route_tests.rs | 50 +++++--- lightning/src/ln/onion_utils.rs | 167 ++++++++++++-------------- 2 files changed, 111 insertions(+), 106 deletions(-) diff --git a/lightning/src/ln/onion_route_tests.rs b/lightning/src/ln/onion_route_tests.rs index f23cfaacdb0..7ab74138068 100644 --- a/lightning/src/ln/onion_route_tests.rs +++ b/lightning/src/ln/onion_route_tests.rs @@ -28,7 +28,7 @@ use crate::ln::msgs::{ OutboundOnionPayload, OutboundTrampolinePayload, MessageSendEvent, }; use crate::ln::wire::Encode; -use crate::util::ser::{Writeable, Writer, BigSize}; +use crate::util::ser::{BigSize, Writeable, Writer}; use crate::util::test_utils; use crate::util::config::{UserConfig, ChannelConfig, MaxDustHTLCExposure}; use crate::util::errors::APIError; @@ -49,6 +49,8 @@ use crate::blinded_path::BlindedHop; use crate::ln::functional_test_utils::*; use crate::ln::onion_utils::{construct_trampoline_onion_keys, construct_trampoline_onion_packet}; +use super::msgs::OnionErrorPacket; + fn run_onion_failure_test(_name: &str, test_case: u8, nodes: &Vec, route: &Route, payment_hash: &PaymentHash, payment_secret: &PaymentSecret, callback_msg: F1, callback_node: F2, expected_retryable: bool, expected_error_code: Option, expected_channel_update: Option, expected_short_channel_id: Option, expected_htlc_destination: Option) where F1: for <'a> FnMut(&'a mut msgs::UpdateAddHTLC), F2: FnMut(), @@ -409,7 +411,7 @@ fn test_onion_failure() { // and tamper returning error message let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); - msg.reason = onion_utils::build_first_hop_failure_packet(onion_keys[0].shared_secret.as_ref(), NODE|2, &[0;0]); + msg.reason = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), NODE|2, &[0;0]); }, ||{}, true, Some(NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[0].pubkey, is_permanent: false}), Some(route.paths[0].hops[0].short_channel_id), Some(next_hop_failure.clone())); // final node failure @@ -417,7 +419,7 @@ fn test_onion_failure() { // and tamper returning error message let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); - msg.reason = onion_utils::build_first_hop_failure_packet(onion_keys[1].shared_secret.as_ref(), NODE|2, &[0;0]); + msg.reason = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), NODE|2, &[0;0]); }, ||{ nodes[2].node.fail_htlc_backwards(&payment_hash); }, true, Some(NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[1].pubkey, is_permanent: false}), Some(route.paths[0].hops[1].short_channel_id), None); @@ -429,14 +431,14 @@ fn test_onion_failure() { }, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); - msg.reason = onion_utils::build_first_hop_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|NODE|2, &[0;0]); + msg.reason = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|NODE|2, &[0;0]); }, ||{}, true, Some(PERM|NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[0].pubkey, is_permanent: true}), Some(route.paths[0].hops[0].short_channel_id), Some(next_hop_failure.clone())); // final node failure run_onion_failure_test_with_fail_intercept("permanent_node_failure", 200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); - msg.reason = onion_utils::build_first_hop_failure_packet(onion_keys[1].shared_secret.as_ref(), PERM|NODE|2, &[0;0]); + msg.reason = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), PERM|NODE|2, &[0;0]); }, ||{ nodes[2].node.fail_htlc_backwards(&payment_hash); }, false, Some(PERM|NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[1].pubkey, is_permanent: true}), Some(route.paths[0].hops[1].short_channel_id), None); @@ -448,7 +450,7 @@ fn test_onion_failure() { }, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); - msg.reason = onion_utils::build_first_hop_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|NODE|3, &[0;0]); + msg.reason = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|NODE|3, &[0;0]); }, ||{ nodes[2].node.fail_htlc_backwards(&payment_hash); }, true, Some(PERM|NODE|3), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[0].pubkey, is_permanent: true}), Some(route.paths[0].hops[0].short_channel_id), Some(next_hop_failure.clone())); @@ -457,7 +459,7 @@ fn test_onion_failure() { run_onion_failure_test_with_fail_intercept("required_node_feature_missing", 200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); - msg.reason = onion_utils::build_first_hop_failure_packet(onion_keys[1].shared_secret.as_ref(), PERM|NODE|3, &[0;0]); + msg.reason = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), PERM|NODE|3, &[0;0]); }, ||{ nodes[2].node.fail_htlc_backwards(&payment_hash); }, false, Some(PERM|NODE|3), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[1].pubkey, is_permanent: true}), Some(route.paths[0].hops[1].short_channel_id), None); @@ -487,7 +489,7 @@ fn test_onion_failure() { }, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); - msg.reason = onion_utils::build_first_hop_failure_packet(onion_keys[0].shared_secret.as_ref(), UPDATE|7, &err_data); + msg.reason = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), UPDATE|7, &err_data); }, ||{}, true, Some(UPDATE|7), Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false }), Some(short_channel_id), Some(next_hop_failure.clone())); @@ -499,7 +501,7 @@ fn test_onion_failure() { }, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); - msg.reason = onion_utils::build_first_hop_failure_packet(onion_keys[0].shared_secret.as_ref(), UPDATE|7, &err_data_without_type); + msg.reason = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), UPDATE|7, &err_data_without_type); }, ||{}, true, Some(UPDATE|7), Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false }), Some(short_channel_id), Some(next_hop_failure.clone())); @@ -510,7 +512,7 @@ fn test_onion_failure() { }, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); - msg.reason = onion_utils::build_first_hop_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|8, &[0;0]); + msg.reason = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|8, &[0;0]); // short_channel_id from the processing node }, ||{}, true, Some(PERM|8), Some(NetworkUpdate::ChannelFailure{short_channel_id, is_permanent: true}), Some(short_channel_id), Some(next_hop_failure.clone())); @@ -520,7 +522,7 @@ fn test_onion_failure() { }, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); - msg.reason = onion_utils::build_first_hop_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|9, &[0;0]); + msg.reason = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|9, &[0;0]); // short_channel_id from the processing node }, ||{}, true, Some(PERM|9), Some(NetworkUpdate::ChannelFailure{short_channel_id, is_permanent: true}), Some(short_channel_id), Some(next_hop_failure.clone())); @@ -652,7 +654,7 @@ fn test_onion_failure() { // Tamper returning error message let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); - msg.reason = onion_utils::build_first_hop_failure_packet(onion_keys[1].shared_secret.as_ref(), 23, &[0;0]); + msg.reason = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), 23, &[0;0]); }, ||{ nodes[2].node.fail_htlc_backwards(&payment_hash); }, true, Some(23), None, None, None); @@ -670,8 +672,12 @@ fn test_onion_failure() { let mut hmac = HmacEngine::::new(&um); hmac.input(&decoded_err_packet.encode()[32..]); decoded_err_packet.hmac = Hmac::from_engine(hmac).to_byte_array(); - msg.reason = onion_utils::encrypt_failure_packet( - &onion_keys[1].shared_secret.as_ref(), &decoded_err_packet.encode()[..]) + let mut onion_error = OnionErrorPacket { + data: decoded_err_packet.encode(), + }; + onion_utils::test_crypt_failure_packet( + &onion_keys[1].shared_secret.as_ref(), &mut onion_error); + msg.reason = onion_error; }, || nodes[2].node.fail_htlc_backwards(&payment_hash), false, None, Some(NetworkUpdate::NodeFailure { node_id: route.paths[0].hops[1].pubkey, is_permanent: true }), Some(channels[1].0.contents.short_channel_id), None); @@ -693,8 +699,12 @@ fn test_onion_failure() { let mut hmac = HmacEngine::::new(&um); hmac.input(&decoded_err_packet.encode()[32..]); decoded_err_packet.hmac = Hmac::from_engine(hmac).to_byte_array(); - msg.reason = onion_utils::encrypt_failure_packet( - &onion_keys[0].shared_secret.as_ref(), &decoded_err_packet.encode()[..]) + let mut onion_error = OnionErrorPacket{ + data: decoded_err_packet.encode(), + }; + onion_utils::test_crypt_failure_packet( + &onion_keys[0].shared_secret.as_ref(), &mut onion_error); + msg.reason = onion_error; }, || {}, true, Some(0x1000|7), Some(NetworkUpdate::ChannelFailure { short_channel_id: channels[1].0.contents.short_channel_id, @@ -717,8 +727,12 @@ fn test_onion_failure() { let mut hmac = HmacEngine::::new(&um); hmac.input(&decoded_err_packet.encode()[32..]); decoded_err_packet.hmac = Hmac::from_engine(hmac).to_byte_array(); - msg.reason = onion_utils::encrypt_failure_packet( - &onion_keys[1].shared_secret.as_ref(), &decoded_err_packet.encode()[..]) + let mut onion_error = OnionErrorPacket{ + data: decoded_err_packet.encode(), + }; + onion_utils::test_crypt_failure_packet( + &onion_keys[1].shared_secret.as_ref(), &mut onion_error); + msg.reason = onion_error; }, || nodes[2].node.fail_htlc_backwards(&payment_hash), true, Some(0x1000|7), Some(NetworkUpdate::ChannelFailure { short_channel_id: channels[1].0.contents.short_channel_id, diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index bc4121cfd3d..6ef167bd03e 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -7,6 +7,7 @@ // You may not use this file except in accordance with one or both of these // licenses. +use super::msgs::OnionErrorPacket; use crate::blinded_path::BlindedHop; use crate::crypto::chacha20::ChaCha20; use crate::crypto::streams::ChaChaReader; @@ -869,23 +870,25 @@ fn construct_onion_packet_with_init_noise( Ok(P::new(onion_keys.first().unwrap().ephemeral_pubkey, packet_data, hmac_res)) } -/// Encrypts a failure packet. raw_packet can either be a -/// msgs::DecodedOnionErrorPacket.encode() result or a msgs::OnionErrorPacket.data element. -pub(super) fn encrypt_failure_packet( - shared_secret: &[u8], raw_packet: &[u8], -) -> msgs::OnionErrorPacket { +/// Encrypts/decrypts a failure packet. +fn crypt_failure_packet(shared_secret: &[u8], packet: &mut OnionErrorPacket) { let ammag = gen_ammag_from_shared_secret(&shared_secret); + process_chacha(&ammag, &mut packet.data); +} - let mut packet_crypted = Vec::with_capacity(raw_packet.len()); - packet_crypted.resize(raw_packet.len(), 0); - let mut chacha = ChaCha20::new(&ammag, &[0u8; 8]); - chacha.process(&raw_packet, &mut packet_crypted[..]); - msgs::OnionErrorPacket { data: packet_crypted } +#[cfg(test)] +pub(super) fn test_crypt_failure_packet(shared_secret: &[u8], packet: &mut OnionErrorPacket) { + crypt_failure_packet(shared_secret, packet) } -pub(super) fn build_failure_packet( +fn process_chacha(key: &[u8; 32], packet: &mut [u8]) { + let mut chacha = ChaCha20::new(key, &[0u8; 8]); + chacha.process_in_place(packet); +} + +fn build_unencrypted_failure_packet( shared_secret: &[u8], failure_type: u16, failure_data: &[u8], -) -> msgs::DecodedOnionErrorPacket { +) -> OnionErrorPacket { assert_eq!(shared_secret.len(), 32); assert!(failure_data.len() <= 256 - 2); @@ -909,15 +912,18 @@ pub(super) fn build_failure_packet( hmac.input(&packet.encode()[32..]); packet.hmac = Hmac::from_engine(hmac).to_byte_array(); - packet + OnionErrorPacket { data: packet.encode() } } -#[cfg(test)] -pub(super) fn build_first_hop_failure_packet( +pub(super) fn build_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); - encrypt_failure_packet(shared_secret, &failure_packet.encode()[..]) +) -> OnionErrorPacket { + let mut onion_error_packet = + build_unencrypted_failure_packet(shared_secret, failure_type, failure_data); + + crypt_failure_packet(shared_secret, &mut onion_error_packet); + + onion_error_packet } pub(crate) struct DecodedOnionFailure { @@ -931,18 +937,12 @@ pub(crate) struct DecodedOnionFailure { pub(crate) onion_error_data: Option>, } -/// Decrypt the error packet in-place. -fn decrypt_onion_error_packet(packet: &mut Vec, shared_secret: SharedSecret) { - let ammag = gen_ammag_from_shared_secret(shared_secret.as_ref()); - let mut chacha = ChaCha20::new(&ammag, &[0u8; 8]); - chacha.process_in_place(packet); -} - /// Process failure we got back from upstream on a payment we sent (implying htlc_source is an /// OutboundRoute). #[inline] pub(super) fn process_onion_failure( - secp_ctx: &Secp256k1, logger: &L, htlc_source: &HTLCSource, mut encrypted_packet: Vec, + secp_ctx: &Secp256k1, logger: &L, htlc_source: &HTLCSource, + mut encrypted_packet: OnionErrorPacket, ) -> DecodedOnionFailure where L::Target: Logger, @@ -975,7 +975,11 @@ where const UPDATE: u16 = 0x1000; // Handle packed channel/node updates for passing back for the route handler - let callback = |shared_secret, _, _, route_hop_opt: Option<&RouteHop>, route_hop_idx| { + let callback = |shared_secret: SharedSecret, + _, + _, + route_hop_opt: Option<&RouteHop>, + route_hop_idx| { if res.is_some() { return; } @@ -1017,9 +1021,9 @@ where { // Actually parse the onion error data in tests so we can check that blinded hops fail // back correctly. - decrypt_onion_error_packet(&mut encrypted_packet, shared_secret); + crypt_failure_packet(shared_secret.as_ref(), &mut encrypted_packet); let err_packet = msgs::DecodedOnionErrorPacket::read(&mut Cursor::new( - &encrypted_packet, + &encrypted_packet.data, )) .unwrap(); error_code_ret = Some(u16::from_be_bytes( @@ -1042,18 +1046,18 @@ where let amt_to_forward = htlc_msat - route_hop.fee_msat; htlc_msat = amt_to_forward; - decrypt_onion_error_packet(&mut encrypted_packet, shared_secret); + crypt_failure_packet(shared_secret.as_ref(), &mut encrypted_packet); let um = gen_um_from_shared_secret(shared_secret.as_ref()); let mut hmac = HmacEngine::::new(&um); - hmac.input(&encrypted_packet[32..]); + hmac.input(&encrypted_packet.data[32..]); - if !fixed_time_eq(&Hmac::from_engine(hmac).to_byte_array(), &encrypted_packet[..32]) { + if !fixed_time_eq(&Hmac::from_engine(hmac).to_byte_array(), &encrypted_packet.data[..32]) { return; } let err_packet = - match msgs::DecodedOnionErrorPacket::read(&mut Cursor::new(&encrypted_packet)) { + match msgs::DecodedOnionErrorPacket::read(&mut Cursor::new(&encrypted_packet.data)) { Ok(p) => p, Err(_) => { log_warn!(logger, "Unreadable failure from {}", route_hop.pubkey); @@ -1366,27 +1370,22 @@ impl HTLCFailReason { match self.0 { HTLCFailReasonRepr::Reason { ref failure_code, ref data } => { if let Some(secondary_shared_secret) = secondary_shared_secret { - let inner_packet = - build_failure_packet(secondary_shared_secret, *failure_code, &data[..]) - .encode(); - let encrypted_inner_packet = - encrypt_failure_packet(secondary_shared_secret, &inner_packet); - encrypt_failure_packet( - incoming_packet_shared_secret, - &encrypted_inner_packet.data[..], - ) + let mut packet = + build_failure_packet(secondary_shared_secret, *failure_code, &data[..]); + + crypt_failure_packet(incoming_packet_shared_secret, &mut packet); + + packet } else { - let packet = build_failure_packet( - incoming_packet_shared_secret, - *failure_code, - &data[..], - ) - .encode(); - encrypt_failure_packet(incoming_packet_shared_secret, &packet) + build_failure_packet(incoming_packet_shared_secret, *failure_code, &data[..]) } }, HTLCFailReasonRepr::LightningError { ref err } => { - encrypt_failure_packet(incoming_packet_shared_secret, &err.data) + let mut err = err.clone(); + + crypt_failure_packet(incoming_packet_shared_secret, &mut err); + + err }, } } @@ -1399,7 +1398,7 @@ impl HTLCFailReason { { match self.0 { HTLCFailReasonRepr::LightningError { ref err } => { - process_onion_failure(secp_ctx, logger, &htlc_source, err.data.clone()) + process_onion_failure(secp_ctx, logger, &htlc_source, err.clone()) }, #[allow(unused)] HTLCFailReasonRepr::Reason { ref failure_code, ref data, .. } => { @@ -2287,45 +2286,33 @@ 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 hex = "4c2fc8bc08510334b6833ad9c3e79cd1b52ae59dfe5c2a4b23ead50f09f7ee0b0002200200fe0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; - assert_eq!(onion_error.encode(), >::from_hex(hex).unwrap()); - - let onion_packet_1 = super::encrypt_failure_packet( + let mut onion_error = super::build_unencrypted_failure_packet( onion_keys[4].shared_secret.as_ref(), - &onion_error.encode()[..], + 0x2002, + &[0; 0], ); + let hex = "4c2fc8bc08510334b6833ad9c3e79cd1b52ae59dfe5c2a4b23ead50f09f7ee0b0002200200fe0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + assert_eq!(onion_error.data, >::from_hex(hex).unwrap()); + + super::crypt_failure_packet(onion_keys[4].shared_secret.as_ref(), &mut onion_error); let hex = "a5e6bd0c74cb347f10cce367f949098f2457d14c046fd8a22cb96efb30b0fdcda8cb9168b50f2fd45edd73c1b0c8b33002df376801ff58aaa94000bf8a86f92620f343baef38a580102395ae3abf9128d1047a0736ff9b83d456740ebbb4aeb3aa9737f18fb4afb4aa074fb26c4d702f42968888550a3bded8c05247e045b866baef0499f079fdaeef6538f31d44deafffdfd3afa2fb4ca9082b8f1c465371a9894dd8c243fb4847e004f5256b3e90e2edde4c9fb3082ddfe4d1e734cacd96ef0706bf63c9984e22dc98851bcccd1c3494351feb458c9c6af41c0044bea3c47552b1d992ae542b17a2d0bba1a096c78d169034ecb55b6e3a7263c26017f033031228833c1daefc0dedb8cf7c3e37c9c37ebfe42f3225c326e8bcfd338804c145b16e34e4"; - assert_eq!(onion_packet_1.data, >::from_hex(hex).unwrap()); + assert_eq!(onion_error.data, >::from_hex(hex).unwrap()); - let onion_packet_2 = super::encrypt_failure_packet( - onion_keys[3].shared_secret.as_ref(), - &onion_packet_1.data[..], - ); + super::crypt_failure_packet(onion_keys[3].shared_secret.as_ref(), &mut onion_error); let hex = "c49a1ce81680f78f5f2000cda36268de34a3f0a0662f55b4e837c83a8773c22aa081bab1616a0011585323930fa5b9fae0c85770a2279ff59ec427ad1bbff9001c0cd1497004bd2a0f68b50704cf6d6a4bf3c8b6a0833399a24b3456961ba00736785112594f65b6b2d44d9f5ea4e49b5e1ec2af978cbe31c67114440ac51a62081df0ed46d4a3df295da0b0fe25c0115019f03f15ec86fabb4c852f83449e812f141a9395b3f70b766ebbd4ec2fae2b6955bd8f32684c15abfe8fd3a6261e52650e8807a92158d9f1463261a925e4bfba44bd20b166d532f0017185c3a6ac7957adefe45559e3072c8dc35abeba835a8cb01a71a15c736911126f27d46a36168ca5ef7dccd4e2886212602b181463e0dd30185c96348f9743a02aca8ec27c0b90dca270"; - assert_eq!(onion_packet_2.data, >::from_hex(hex).unwrap()); + assert_eq!(onion_error.data, >::from_hex(hex).unwrap()); - let onion_packet_3 = super::encrypt_failure_packet( - onion_keys[2].shared_secret.as_ref(), - &onion_packet_2.data[..], - ); + super::crypt_failure_packet(onion_keys[2].shared_secret.as_ref(), &mut onion_error); let hex = "a5d3e8634cfe78b2307d87c6d90be6fe7855b4f2cc9b1dfb19e92e4b79103f61ff9ac25f412ddfb7466e74f81b3e545563cdd8f5524dae873de61d7bdfccd496af2584930d2b566b4f8d3881f8c043df92224f38cf094cfc09d92655989531524593ec6d6caec1863bdfaa79229b5020acc034cd6deeea1021c50586947b9b8e6faa83b81fbfa6133c0af5d6b07c017f7158fa94f0d206baf12dda6b68f785b773b360fd0497e16cc402d779c8d48d0fa6315536ef0660f3f4e1865f5b38ea49c7da4fd959de4e83ff3ab686f059a45c65ba2af4a6a79166aa0f496bf04d06987b6d2ea205bdb0d347718b9aeff5b61dfff344993a275b79717cd815b6ad4c0beb568c4ac9c36ff1c315ec1119a1993c4b61e6eaa0375e0aaf738ac691abd3263bf937e3"; - assert_eq!(onion_packet_3.data, >::from_hex(hex).unwrap()); + assert_eq!(onion_error.data, >::from_hex(hex).unwrap()); - let onion_packet_4 = super::encrypt_failure_packet( - onion_keys[1].shared_secret.as_ref(), - &onion_packet_3.data[..], - ); + super::crypt_failure_packet(onion_keys[1].shared_secret.as_ref(), &mut onion_error); let hex = "aac3200c4968f56b21f53e5e374e3a2383ad2b1b6501bbcc45abc31e59b26881b7dfadbb56ec8dae8857add94e6702fb4c3a4de22e2e669e1ed926b04447fc73034bb730f4932acd62727b75348a648a1128744657ca6a4e713b9b646c3ca66cac02cdab44dd3439890ef3aaf61708714f7375349b8da541b2548d452d84de7084bb95b3ac2345201d624d31f4d52078aa0fa05a88b4e20202bd2b86ac5b52919ea305a8949de95e935eed0319cf3cf19ebea61d76ba92532497fcdc9411d06bcd4275094d0a4a3c5d3a945e43305a5a9256e333e1f64dbca5fcd4e03a39b9012d197506e06f29339dfee3331995b21615337ae060233d39befea925cc262873e0530408e6990f1cbd233a150ef7b004ff6166c70c68d9f8c853c1abca640b8660db2921"; - assert_eq!(onion_packet_4.data, >::from_hex(hex).unwrap()); + assert_eq!(onion_error.data, >::from_hex(hex).unwrap()); - let onion_packet_5 = super::encrypt_failure_packet( - onion_keys[0].shared_secret.as_ref(), - &onion_packet_4.data[..], - ); + super::crypt_failure_packet(onion_keys[0].shared_secret.as_ref(), &mut onion_error); let hex = "9c5add3963fc7f6ed7f148623c84134b5647e1306419dbe2174e523fa9e2fbed3a06a19f899145610741c83ad40b7712aefaddec8c6baf7325d92ea4ca4d1df8bce517f7e54554608bf2bd8071a4f52a7a2f7ffbb1413edad81eeea5785aa9d990f2865dc23b4bc3c301a94eec4eabebca66be5cf638f693ec256aec514620cc28ee4a94bd9565bc4d4962b9d3641d4278fb319ed2b84de5b665f307a2db0f7fbb757366067d88c50f7e829138fde4f78d39b5b5802f1b92a8a820865af5cc79f9f30bc3f461c66af95d13e5e1f0381c184572a91dee1c849048a647a1158cf884064deddbf1b0b88dfe2f791428d0ba0f6fb2f04e14081f69165ae66d9297c118f0907705c9c4954a199bae0bb96fad763d690e7daa6cfda59ba7f2c8d11448b604d12d"; - assert_eq!(onion_packet_5.data, >::from_hex(hex).unwrap()); + assert_eq!(onion_error.data, >::from_hex(hex).unwrap()); let logger: Arc = Arc::new(TestLogger::new()); let ctx_full = Secp256k1::new(); @@ -2339,7 +2326,7 @@ mod tests { // Assert that the original failure can be retrieved and that all hmacs check out. let decrypted_failure = - process_onion_failure(&ctx_full, &logger, &htlc_source, onion_packet_5.data); + process_onion_failure(&ctx_full, &logger, &htlc_source, onion_error); assert_eq!(decrypted_failure.onion_error_code, Some(0x2002)); } @@ -2348,10 +2335,11 @@ mod tests { fn test_non_attributable_failure_packet_onion() { // Create a failure packet with bogus data. let packet = vec![1u8; 292]; + let onion_error_packet = OnionErrorPacket { data: packet }; // In the current protocol, it is unfortunately not possible to identify the failure source. let logger: TestLogger = TestLogger::new(); - let decrypted_failure = test_failure_attribution(&logger, &packet); + let decrypted_failure = test_failure_attribution(&logger, onion_error_packet); assert_eq!(decrypted_failure.short_channel_id, None); logger.assert_log_contains( @@ -2376,11 +2364,12 @@ mod tests { let hmac = Hmac::from_engine(hmac).to_byte_array(); packet[..32].copy_from_slice(&hmac); - let packet = encrypt_failure_packet(shared_secret, &packet); + let mut onion_error_packet = OnionErrorPacket { data: packet.to_vec() }; + crypt_failure_packet(shared_secret, &mut onion_error_packet); // For the unreadable failure, it is still expected that the failing channel can be identified. let logger: TestLogger = TestLogger::new(); - let decrypted_failure = test_failure_attribution(&logger, &packet.data); + let decrypted_failure = test_failure_attribution(&logger, onion_error_packet); assert_eq!(decrypted_failure.short_channel_id, Some(0)); logger.assert_log_contains("lightning::ln::onion_utils", "Unreadable failure", 1); @@ -2401,10 +2390,11 @@ mod tests { hmac.input(&packet.encode()[32..]); packet.hmac = Hmac::from_engine(hmac).to_byte_array(); - let packet = encrypt_failure_packet(shared_secret, &packet.encode()[..]); + let mut onion_error_packet = OnionErrorPacket { data: packet.encode() }; + crypt_failure_packet(shared_secret, &mut onion_error_packet); let logger = TestLogger::new(); - let decrypted_failure = test_failure_attribution(&logger, &packet.data); + let decrypted_failure = test_failure_attribution(&logger, onion_error_packet); assert_eq!(decrypted_failure.short_channel_id, Some(0)); logger.assert_log_contains( @@ -2414,7 +2404,9 @@ mod tests { ); } - fn test_failure_attribution(logger: &TestLogger, packet: &[u8]) -> DecodedOnionFailure { + fn test_failure_attribution( + logger: &TestLogger, packet: OnionErrorPacket, + ) -> DecodedOnionFailure { let ctx_full = Secp256k1::new(); let path = build_test_path(); let htlc_source = HTLCSource::OutboundRoute { @@ -2424,8 +2416,7 @@ mod tests { payment_id: PaymentId([1; 32]), }; - let decrypted_failure = - process_onion_failure(&ctx_full, &logger, &htlc_source, packet.into()); + let decrypted_failure = process_onion_failure(&ctx_full, &logger, &htlc_source, packet); decrypted_failure } From 1426197828384463e972cad43ee031f3984cad93 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Wed, 5 Mar 2025 09:12:20 +0100 Subject: [PATCH 2/2] Attributable failures pre-factor This commits prepares the persistence layer for the addition of attribution data. Changes: - Expand InboundHTLCRemovalReason serialization instead of using the macro. When attribution data is added, it can't just be serialized along with the existing fields because it would break backwards compatibility. Instead the new field needs to go into the tlv block. - Stop using OnionErrorPacket in the UpdateFailHTLC message. When attribution data is added to OnionErrorPacket, it would not be serialized for the wire properly because also here the new field needs to go in the tlv extension of the message. - Prepare HTLCFailReasonRepr serialization for that addition of attribution data. Co-authored-by: Matt Corallo --- lightning/src/ln/channel.rs | 51 ++++++++++++++++++--------- lightning/src/ln/channelmanager.rs | 23 ++++++------ lightning/src/ln/functional_tests.rs | 2 +- lightning/src/ln/msgs.rs | 24 ++++++------- lightning/src/ln/onion_payment.rs | 7 ++-- lightning/src/ln/onion_route_tests.rs | 39 ++++++++++++-------- lightning/src/ln/onion_utils.rs | 13 +++++-- 7 files changed, 100 insertions(+), 59 deletions(-) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index a09b3bfa77a..8625eb6914a 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -35,7 +35,7 @@ use crate::ln::interactivetxs::{ TX_COMMON_FIELDS_WEIGHT, }; use crate::ln::msgs; -use crate::ln::msgs::{ClosingSigned, ClosingSignedFeeRange, DecodeError}; +use crate::ln::msgs::{ClosingSigned, ClosingSignedFeeRange, DecodeError, OnionErrorPacket}; use crate::ln::script::{self, ShutdownScript}; use crate::ln::channel_state::{ChannelShutdownState, CounterpartyForwardingInfo, InboundHTLCDetails, InboundHTLCStateDetails, OutboundHTLCDetails, OutboundHTLCStateDetails}; use crate::ln::channelmanager::{self, OpenChannelMessage, PendingHTLCStatus, HTLCSource, SentHTLCId, HTLCFailureMsg, PendingHTLCInfo, RAACommitmentOrder, PaymentClaimDetails, BREAKDOWN_TIMEOUT, MIN_CLTV_EXPIRY_DELTA, MAX_LOCAL_BREAKDOWN_TIMEOUT}; @@ -50,7 +50,7 @@ use crate::ln::chan_utils::{ #[cfg(splicing)] use crate::ln::chan_utils::FUNDING_TRANSACTION_WITNESS_WEIGHT; use crate::ln::chan_utils; -use crate::ln::onion_utils::HTLCFailReason; +use crate::ln::onion_utils::{HTLCFailReason}; use crate::chain::BestBlock; use crate::chain::chaininterface::{FeeEstimator, ConfirmationTarget, LowerBoundedFeeEstimator, fee_for_weight}; use crate::chain::channelmonitor::{ChannelMonitor, ChannelMonitorUpdate, ChannelMonitorUpdateStep, LATENCY_GRACE_PERIOD_BLOCKS}; @@ -4933,7 +4933,7 @@ trait FailHTLCContents { impl FailHTLCContents for msgs::OnionErrorPacket { type Message = msgs::UpdateFailHTLC; fn to_message(self, htlc_id: u64, channel_id: ChannelId) -> Self::Message { - msgs::UpdateFailHTLC { htlc_id, channel_id, reason: self } + msgs::UpdateFailHTLC { htlc_id, channel_id, reason: self.data } } fn to_inbound_htlc_state(self) -> InboundHTLCState { InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailRelay(self)) @@ -6136,7 +6136,7 @@ impl FundedChannel where require_commitment = true; match fail_msg { HTLCFailureMsg::Relay(msg) => { - htlc.state = InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailRelay(msg.reason.clone())); + htlc.state = InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailRelay(msg.clone().into())); update_fail_htlcs.push(msg) }, HTLCFailureMsg::Malformed(msg) => { @@ -6844,7 +6844,7 @@ impl FundedChannel where update_fail_htlcs.push(msgs::UpdateFailHTLC { channel_id: self.context.channel_id(), htlc_id: htlc.htlc_id, - reason: err_packet.clone() + reason: err_packet.data.clone(), }); }, &InboundHTLCRemovalReason::FailMalformed((ref sha256_of_onion, ref failure_code)) => { @@ -10142,11 +10142,6 @@ fn get_initial_channel_type(config: &UserConfig, their_features: &InitFeatures) const SERIALIZATION_VERSION: u8 = 4; const MIN_SERIALIZATION_VERSION: u8 = 4; -impl_writeable_tlv_based_enum_legacy!(InboundHTLCRemovalReason,; - (0, FailRelay), - (1, FailMalformed), - (2, Fulfill), -); impl Writeable for ChannelUpdateStatus { fn write(&self, writer: &mut W) -> Result<(), io::Error> { @@ -10276,7 +10271,20 @@ impl Writeable for FundedChannel where SP::Target: SignerProvider }, &InboundHTLCState::LocalRemoved(ref removal_reason) => { 4u8.write(writer)?; - removal_reason.write(writer)?; + match removal_reason { + InboundHTLCRemovalReason::FailRelay(msgs::OnionErrorPacket { data }) => { + 0u8.write(writer)?; + data.write(writer)?; + }, + InboundHTLCRemovalReason::FailMalformed((hash, code)) => { + 1u8.write(writer)?; + (hash, code).write(writer)?; + }, + InboundHTLCRemovalReason::Fulfill(preimage) => { + 2u8.write(writer)?; + preimage.write(writer)?; + }, + } }, } } @@ -10355,7 +10363,7 @@ impl Writeable for FundedChannel where SP::Target: SignerProvider &HTLCUpdateAwaitingACK::FailHTLC { ref htlc_id, ref err_packet } => { 2u8.write(writer)?; htlc_id.write(writer)?; - err_packet.write(writer)?; + err_packet.data.write(writer)?; } &HTLCUpdateAwaitingACK::FailMalformedHTLC { htlc_id, failure_code, sha256_of_onion @@ -10364,10 +10372,9 @@ impl Writeable for FundedChannel where SP::Target: SignerProvider // `::FailHTLC` variant and write the real malformed error as an optional TLV. malformed_htlcs.push((htlc_id, failure_code, sha256_of_onion)); - let dummy_err_packet = msgs::OnionErrorPacket { data: Vec::new() }; 2u8.write(writer)?; htlc_id.write(writer)?; - dummy_err_packet.write(writer)?; + Vec::::new().write(writer)?; } } } @@ -10613,7 +10620,17 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel InboundHTLCState::AwaitingAnnouncedRemoteRevoke(resolution) }, 3 => InboundHTLCState::Committed, - 4 => InboundHTLCState::LocalRemoved(Readable::read(reader)?), + 4 => { + let reason = match ::read(reader)? { + 0 => InboundHTLCRemovalReason::FailRelay(msgs::OnionErrorPacket { + data: Readable::read(reader)?, + }), + 1 => InboundHTLCRemovalReason::FailMalformed(Readable::read(reader)?), + 2 => InboundHTLCRemovalReason::Fulfill(Readable::read(reader)?), + _ => return Err(DecodeError::InvalidValue), + }; + InboundHTLCState::LocalRemoved(reason) + }, _ => return Err(DecodeError::InvalidValue), }, }); @@ -10669,7 +10686,9 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel }, 2 => HTLCUpdateAwaitingACK::FailHTLC { htlc_id: Readable::read(reader)?, - err_packet: Readable::read(reader)?, + err_packet: OnionErrorPacket { + data: Readable::read(reader)?, + }, }, _ => return Err(DecodeError::InvalidValue), }); diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index c69e6c6b02b..4574b2e7f8a 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -4470,11 +4470,12 @@ where } else { (err_code, &res.0[..]) }; + let failure = HTLCFailReason::reason(err_code, err_data.to_vec()) + .get_encrypted_failure_packet(shared_secret, &None); HTLCFailureMsg::Relay(msgs::UpdateFailHTLC { channel_id: msg.channel_id, htlc_id: msg.htlc_id, - reason: HTLCFailReason::reason(err_code, err_data.to_vec()) - .get_encrypted_failure_packet(shared_secret, &None), + reason: failure.data, }) } @@ -4498,11 +4499,12 @@ where } )) } + let failure = HTLCFailReason::reason($err_code, $data.to_vec()) + .get_encrypted_failure_packet(&shared_secret, &None); 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), + reason: failure.data, })); } } @@ -5881,7 +5883,7 @@ where let failure = match htlc_fail { HTLCFailureMsg::Relay(fail_htlc) => HTLCForwardInfo::FailHTLC { htlc_id: fail_htlc.htlc_id, - err_packet: fail_htlc.reason, + err_packet: fail_htlc.into(), }, HTLCFailureMsg::Malformed(fail_malformed_htlc) => HTLCForwardInfo::FailMalformedHTLC { htlc_id: fail_malformed_htlc.htlc_id, @@ -13224,7 +13226,7 @@ impl Writeable for HTLCForwardInfo { FAIL_HTLC_VARIANT_ID.write(w)?; write_tlv_fields!(w, { (0, htlc_id, required), - (2, err_packet, required), + (2, err_packet.data, required), }); }, Self::FailMalformedHTLC { htlc_id, failure_code, sha256_of_onion } => { @@ -13232,11 +13234,10 @@ impl Writeable for HTLCForwardInfo { // packet so older versions have something to fail back with, but serialize the real data as // optional TLVs for the benefit of newer versions. FAIL_HTLC_VARIANT_ID.write(w)?; - let dummy_err_packet = msgs::OnionErrorPacket { data: Vec::new() }; write_tlv_fields!(w, { (0, htlc_id, required), (1, failure_code, required), - (2, dummy_err_packet, required), + (2, Vec::::new(), required), (3, sha256_of_onion, required), }); }, @@ -13266,7 +13267,9 @@ impl Readable for HTLCForwardInfo { } else { Self::FailHTLC { htlc_id: _init_tlv_based_struct_field!(htlc_id, required), - err_packet: _init_tlv_based_struct_field!(err_packet, required), + err_packet: crate::ln::msgs::OnionErrorPacket { + data: _init_tlv_based_struct_field!(err_packet, required), + }, } } }, @@ -16273,7 +16276,7 @@ mod tests { let mut nodes = create_network(1, &node_cfg, &chanmgrs); let dummy_failed_htlc = |htlc_id| { - HTLCForwardInfo::FailHTLC { htlc_id, err_packet: msgs::OnionErrorPacket { data: vec![42] }, } + HTLCForwardInfo::FailHTLC { htlc_id, err_packet: msgs::OnionErrorPacket { data: vec![42] } } }; let dummy_malformed_htlc = |htlc_id| { HTLCForwardInfo::FailMalformedHTLC { htlc_id, failure_code: 0x4000, sha256_of_onion: [0; 32] } diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index d46fc721c13..a65646e9b61 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -7057,7 +7057,7 @@ pub fn test_update_fulfill_htlc_bolt2_update_fail_htlc_before_commitment() { let update_msg = msgs::UpdateFailHTLC{ channel_id: chan.2, htlc_id: 0, - reason: msgs::OnionErrorPacket { data: Vec::new()}, + reason: Vec::new(), }; nodes[0].node.handle_update_fail_htlc(nodes[1].node.get_our_node_id(), &update_msg); diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index 85cbd3b8b1b..4d2102171eb 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -766,7 +766,7 @@ pub struct UpdateFailHTLC { pub channel_id: ChannelId, /// The HTLC ID pub htlc_id: u64, - pub(crate) reason: OnionErrorPacket, + pub(crate) reason: Vec, } /// An [`update_fail_malformed_htlc`] message to be sent to or received from a peer. @@ -2355,6 +2355,14 @@ pub(crate) struct OnionErrorPacket { pub(crate) data: Vec, } +impl From for OnionErrorPacket { + fn from(msg: UpdateFailHTLC) -> Self { + OnionErrorPacket { + data: msg.reason, + } + } +} + impl fmt::Display for DecodeError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { @@ -3001,13 +3009,6 @@ impl_writeable_msg!(PeerStorageRetrieval, { data }, {}); -// Note that this is written as a part of ChannelManager objects, and thus cannot change its -// serialization format in a way which assumes we know the total serialized length/message end -// position. -impl_writeable!(OnionErrorPacket, { - data -}); - // Note that this is written as a part of ChannelManager objects, and thus cannot change its // serialization format in a way which assumes we know the total serialized length/message end // position. @@ -3933,7 +3934,7 @@ mod tests { use crate::ln::types::ChannelId; use crate::types::payment::{PaymentPreimage, PaymentHash, PaymentSecret}; use crate::types::features::{ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures}; - use crate::ln::msgs::{self, FinalOnionHopData, OnionErrorPacket, CommonOpenChannelFields, CommonAcceptChannelFields, OutboundTrampolinePayload, TrampolineOnionPacket, InboundOnionForwardPayload, InboundOnionReceivePayload}; + use crate::ln::msgs::{self, FinalOnionHopData, CommonOpenChannelFields, CommonAcceptChannelFields, OutboundTrampolinePayload, TrampolineOnionPacket, InboundOnionForwardPayload, InboundOnionReceivePayload}; use crate::ln::msgs::SocketAddress; use crate::routing::gossip::{NodeAlias, NodeId}; use crate::util::ser::{BigSize, FixedLengthReader, Hostname, LengthReadable, Readable, ReadableArgs, TransactionU16LenLimited, Writeable}; @@ -4932,13 +4933,10 @@ mod tests { #[test] fn encoding_update_fail_htlc() { - let reason = OnionErrorPacket { - data: [1; 32].to_vec(), - }; let update_fail_htlc = msgs::UpdateFailHTLC { channel_id: ChannelId::from_bytes([2; 32]), htlc_id: 2316138423780173, - reason + reason: [1; 32].to_vec() }; let encoded_value = update_fail_htlc.encode(); let target_value = >::from_hex("020202020202020202020202020202020202020202020202020202020202020200083a840000034d00200101010101010101010101010101010101010101010101010101010101010101").unwrap(); diff --git a/lightning/src/ln/onion_payment.rs b/lightning/src/ln/onion_payment.rs index c3594a9b0d1..46661df6807 100644 --- a/lightning/src/ln/onion_payment.rs +++ b/lightning/src/ln/onion_payment.rs @@ -407,7 +407,7 @@ where ).map_err(|e| { let (err_code, err_data) = match e { HTLCFailureMsg::Malformed(m) => (m.failure_code, Vec::new()), - HTLCFailureMsg::Relay(r) => (0x4000 | 22, r.reason.data), + HTLCFailureMsg::Relay(r) => (0x4000 | 22, r.reason), }; let msg = "Failed to decode update add htlc onion"; InboundHTLCErr { msg, err_code, err_data } @@ -512,11 +512,12 @@ where } log_info!(logger, "Failed to accept/forward incoming HTLC: {}", message); + let failure = HTLCFailReason::reason(err_code, data.to_vec()) + .get_encrypted_failure_packet(&shared_secret, &trampoline_shared_secret); return Err(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, &trampoline_shared_secret), + reason: failure.data, })); }; diff --git a/lightning/src/ln/onion_route_tests.rs b/lightning/src/ln/onion_route_tests.rs index 7ab74138068..127d4a45588 100644 --- a/lightning/src/ln/onion_route_tests.rs +++ b/lightning/src/ln/onion_route_tests.rs @@ -411,7 +411,8 @@ fn test_onion_failure() { // and tamper returning error message let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); - msg.reason = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), NODE|2, &[0;0]); + let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), NODE|2, &[0;0]); + msg.reason = failure.data; }, ||{}, true, Some(NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[0].pubkey, is_permanent: false}), Some(route.paths[0].hops[0].short_channel_id), Some(next_hop_failure.clone())); // final node failure @@ -419,7 +420,8 @@ fn test_onion_failure() { // and tamper returning error message let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); - msg.reason = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), NODE|2, &[0;0]); + let failure = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), NODE|2, &[0;0]); + msg.reason = failure.data; }, ||{ nodes[2].node.fail_htlc_backwards(&payment_hash); }, true, Some(NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[1].pubkey, is_permanent: false}), Some(route.paths[0].hops[1].short_channel_id), None); @@ -431,14 +433,16 @@ fn test_onion_failure() { }, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); - msg.reason = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|NODE|2, &[0;0]); + let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|NODE|2, &[0;0]); + msg.reason = failure.data; }, ||{}, true, Some(PERM|NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[0].pubkey, is_permanent: true}), Some(route.paths[0].hops[0].short_channel_id), Some(next_hop_failure.clone())); // final node failure run_onion_failure_test_with_fail_intercept("permanent_node_failure", 200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); - msg.reason = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), PERM|NODE|2, &[0;0]); + let failure = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), PERM|NODE|2, &[0;0]); + msg.reason = failure.data; }, ||{ nodes[2].node.fail_htlc_backwards(&payment_hash); }, false, Some(PERM|NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[1].pubkey, is_permanent: true}), Some(route.paths[0].hops[1].short_channel_id), None); @@ -450,7 +454,8 @@ fn test_onion_failure() { }, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); - msg.reason = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|NODE|3, &[0;0]); + let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|NODE|3, &[0;0]); + msg.reason = failure.data; }, ||{ nodes[2].node.fail_htlc_backwards(&payment_hash); }, true, Some(PERM|NODE|3), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[0].pubkey, is_permanent: true}), Some(route.paths[0].hops[0].short_channel_id), Some(next_hop_failure.clone())); @@ -459,7 +464,8 @@ fn test_onion_failure() { run_onion_failure_test_with_fail_intercept("required_node_feature_missing", 200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); - msg.reason = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), PERM|NODE|3, &[0;0]); + let failure = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), PERM|NODE|3, &[0;0]); + msg.reason = failure.data; }, ||{ nodes[2].node.fail_htlc_backwards(&payment_hash); }, false, Some(PERM|NODE|3), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[1].pubkey, is_permanent: true}), Some(route.paths[0].hops[1].short_channel_id), None); @@ -489,7 +495,8 @@ fn test_onion_failure() { }, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); - msg.reason = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), UPDATE|7, &err_data); + let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), UPDATE|7, &err_data); + msg.reason = failure.data; }, ||{}, true, Some(UPDATE|7), Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false }), Some(short_channel_id), Some(next_hop_failure.clone())); @@ -501,7 +508,8 @@ fn test_onion_failure() { }, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); - msg.reason = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), UPDATE|7, &err_data_without_type); + let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), UPDATE|7, &err_data_without_type); + msg.reason = failure.data; }, ||{}, true, Some(UPDATE|7), Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false }), Some(short_channel_id), Some(next_hop_failure.clone())); @@ -512,7 +520,8 @@ fn test_onion_failure() { }, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); - msg.reason = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|8, &[0;0]); + let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|8, &[0;0]); + msg.reason = failure.data; // short_channel_id from the processing node }, ||{}, true, Some(PERM|8), Some(NetworkUpdate::ChannelFailure{short_channel_id, is_permanent: true}), Some(short_channel_id), Some(next_hop_failure.clone())); @@ -522,7 +531,8 @@ fn test_onion_failure() { }, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); - msg.reason = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|9, &[0;0]); + let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|9, &[0;0]); + msg.reason = failure.data; // short_channel_id from the processing node }, ||{}, true, Some(PERM|9), Some(NetworkUpdate::ChannelFailure{short_channel_id, is_permanent: true}), Some(short_channel_id), Some(next_hop_failure.clone())); @@ -654,7 +664,8 @@ fn test_onion_failure() { // Tamper returning error message let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); - msg.reason = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), 23, &[0;0]); + let failure = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), 23, &[0;0]); + msg.reason = failure.data; }, ||{ nodes[2].node.fail_htlc_backwards(&payment_hash); }, true, Some(23), None, None, None); @@ -677,7 +688,7 @@ fn test_onion_failure() { }; onion_utils::test_crypt_failure_packet( &onion_keys[1].shared_secret.as_ref(), &mut onion_error); - msg.reason = onion_error; + msg.reason = onion_error.data; }, || nodes[2].node.fail_htlc_backwards(&payment_hash), false, None, Some(NetworkUpdate::NodeFailure { node_id: route.paths[0].hops[1].pubkey, is_permanent: true }), Some(channels[1].0.contents.short_channel_id), None); @@ -704,7 +715,7 @@ fn test_onion_failure() { }; onion_utils::test_crypt_failure_packet( &onion_keys[0].shared_secret.as_ref(), &mut onion_error); - msg.reason = onion_error; + msg.reason = onion_error.data; }, || {}, true, Some(0x1000|7), Some(NetworkUpdate::ChannelFailure { short_channel_id: channels[1].0.contents.short_channel_id, @@ -732,7 +743,7 @@ fn test_onion_failure() { }; onion_utils::test_crypt_failure_packet( &onion_keys[1].shared_secret.as_ref(), &mut onion_error); - msg.reason = onion_error; + msg.reason = onion_error.data; }, || nodes[2].node.fail_htlc_backwards(&payment_hash), true, Some(0x1000|7), Some(NetworkUpdate::ChannelFailure { short_channel_id: channels[1].0.contents.short_channel_id, diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index 6ef167bd03e..41701b1ade3 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -1299,7 +1299,14 @@ impl Readable for HTLCFailReason { impl_writeable_tlv_based_enum!(HTLCFailReasonRepr, (0, LightningError) => { - (0, err, required), + (0, data, (legacy, Vec, |us| + if let &HTLCFailReasonRepr::LightningError { err: msgs::OnionErrorPacket { ref data, .. } } = us { + Some(data) + } else { + None + }) + ), + (_unused, err, (static_value, msgs::OnionErrorPacket { data: data.ok_or(DecodeError::InvalidValue)? })), }, (1, Reason) => { (0, failure_code, required), @@ -1356,7 +1363,9 @@ impl HTLCFailReason { } pub(super) fn from_msg(msg: &msgs::UpdateFailHTLC) -> Self { - Self(HTLCFailReasonRepr::LightningError { err: msg.reason.clone() }) + Self(HTLCFailReasonRepr::LightningError { + err: OnionErrorPacket { data: msg.reason.clone() }, + }) } /// Encrypted a failure packet using a shared secret.