Skip to content

Commit e135632

Browse files
committed
Attributable failures
This commit extends the generation, forwarding and interpretation of htlc fail messages with attribution data. This allows senders to identify the failing node even when this node does not want to be identified and is generating a failure message without a valid hmac. For more information, see the bolt spec.
1 parent fe4daa5 commit e135632

File tree

5 files changed

+470
-77
lines changed

5 files changed

+470
-77
lines changed

fuzz/src/process_onion_failure.rs

+9-2
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,16 @@ fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
118118
HTLCSource::OutboundRoute { path, session_priv, first_hop_htlc_msat: 0, payment_id };
119119

120120
let failure_len = get_u16!();
121+
let attribution_data = if get_bool!() {
122+
Some(lightning::ln::AttributionData {
123+
hold_times: get_slice!(80).try_into().unwrap(),
124+
hmacs: get_slice!(840).try_into().unwrap(),
125+
})
126+
} else {
127+
None
128+
};
121129
let encrypted_packet =
122-
OnionErrorPacket { data: get_slice!(failure_len).into(), attribution_data: None };
123-
130+
OnionErrorPacket { data: get_slice!(failure_len).into(), attribution_data };
124131
lightning::ln::process_onion_failure(&secp_ctx, &logger, &htlc_source, encrypted_packet);
125132
}
126133

lightning/src/ln/functional_tests.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ use crate::util::errors::APIError;
3838
use crate::util::ser::{Writeable, ReadableArgs};
3939
use crate::util::string::UntrustedString;
4040
use crate::util::config::{ChannelConfigOverrides, ChannelHandshakeConfigUpdate, ChannelConfigUpdate, MaxDustHTLCExposure, UserConfig};
41+
use crate::ln::onion_utils::AttributionData;
4142

4243
use bitcoin::hash_types::BlockHash;
4344
use bitcoin::locktime::absolute::LockTime;
@@ -7114,7 +7115,7 @@ pub fn test_update_fulfill_htlc_bolt2_update_fail_htlc_before_commitment() {
71147115
channel_id: chan.2,
71157116
htlc_id: 0,
71167117
reason: Vec::new(),
7117-
attribution_data: None,
7118+
attribution_data: Some(AttributionData::new())
71187119
};
71197120

71207121
nodes[0].node.handle_update_fail_htlc(nodes[1].node.get_our_node_id(), &update_msg);

lightning/src/ln/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ pub use onion_utils::create_payment_onion;
5454
#[cfg(fuzzing)]
5555
pub use onion_utils::process_onion_failure;
5656

57+
#[cfg(fuzzing)]
58+
pub use onion_utils::AttributionData;
59+
5760
#[cfg(test)]
5861
#[allow(unused_mut)]
5962
pub mod bolt11_payment_tests;

lightning/src/ln/onion_route_tests.rs

+32-14
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ use crate::ln::functional_test_utils::*;
5050
use crate::ln::onion_utils::{construct_trampoline_onion_keys, construct_trampoline_onion_packet};
5151

5252
use super::msgs::OnionErrorPacket;
53+
use super::onion_utils::AttributionData;
5354

5455
fn run_onion_failure_test<F1,F2>(_name: &str, test_case: u8, nodes: &Vec<Node>, route: &Route, payment_hash: &PaymentHash, payment_secret: &PaymentSecret, callback_msg: F1, callback_node: F2, expected_retryable: bool, expected_error_code: Option<u16>, expected_channel_update: Option<NetworkUpdate>, expected_short_channel_id: Option<u64>, expected_htlc_destination: Option<HTLCDestination>)
5556
where F1: for <'a> FnMut(&'a mut msgs::UpdateAddHTLC),
@@ -411,17 +412,19 @@ fn test_onion_failure() {
411412
// and tamper returning error message
412413
let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
413414
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
414-
let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), NODE|2, &[0;0]);
415+
let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), NODE|2, &[0;0], 0);
415416
msg.reason = failure.data;
417+
msg.attribution_data = failure.attribution_data;
416418
}, ||{}, 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()));
417419

418420
// final node failure
419421
run_onion_failure_test_with_fail_intercept("temporary_node_failure", 200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| {
420422
// and tamper returning error message
421423
let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
422424
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
423-
let failure = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), NODE|2, &[0;0]);
425+
let failure = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), NODE|2, &[0;0], 0);
424426
msg.reason = failure.data;
427+
msg.attribution_data = failure.attribution_data;
425428
}, ||{
426429
nodes[2].node.fail_htlc_backwards(&payment_hash);
427430
}, 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);
@@ -433,16 +436,18 @@ fn test_onion_failure() {
433436
}, |msg| {
434437
let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
435438
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
436-
let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|NODE|2, &[0;0]);
439+
let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|NODE|2, &[0;0], 0);
437440
msg.reason = failure.data;
441+
msg.attribution_data = failure.attribution_data;
438442
}, ||{}, 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()));
439443

440444
// final node failure
441445
run_onion_failure_test_with_fail_intercept("permanent_node_failure", 200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| {
442446
let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
443447
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
444-
let failure = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), PERM|NODE|2, &[0;0]);
448+
let failure = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), PERM|NODE|2, &[0;0], 0);
445449
msg.reason = failure.data;
450+
msg.attribution_data = failure.attribution_data;
446451
}, ||{
447452
nodes[2].node.fail_htlc_backwards(&payment_hash);
448453
}, 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);
@@ -454,8 +459,9 @@ fn test_onion_failure() {
454459
}, |msg| {
455460
let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
456461
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
457-
let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|NODE|3, &[0;0]);
462+
let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|NODE|3, &[0;0], 0);
458463
msg.reason = failure.data;
464+
msg.attribution_data = failure.attribution_data;
459465
}, ||{
460466
nodes[2].node.fail_htlc_backwards(&payment_hash);
461467
}, 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()));
@@ -464,8 +470,9 @@ fn test_onion_failure() {
464470
run_onion_failure_test_with_fail_intercept("required_node_feature_missing", 200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| {
465471
let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
466472
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
467-
let failure = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), PERM|NODE|3, &[0;0]);
473+
let failure = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), PERM|NODE|3, &[0;0], 0);
468474
msg.reason = failure.data;
475+
msg.attribution_data = failure.attribution_data;
469476
}, ||{
470477
nodes[2].node.fail_htlc_backwards(&payment_hash);
471478
}, 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);
@@ -495,8 +502,9 @@ fn test_onion_failure() {
495502
}, |msg| {
496503
let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
497504
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
498-
let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), UPDATE|7, &err_data);
505+
let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), UPDATE|7, &err_data, 0);
499506
msg.reason = failure.data;
507+
msg.attribution_data = failure.attribution_data;
500508
}, ||{}, true, Some(UPDATE|7),
501509
Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false }),
502510
Some(short_channel_id), Some(next_hop_failure.clone()));
@@ -508,8 +516,9 @@ fn test_onion_failure() {
508516
}, |msg| {
509517
let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
510518
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
511-
let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), UPDATE|7, &err_data_without_type);
519+
let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), UPDATE|7, &err_data_without_type, 0);
512520
msg.reason = failure.data;
521+
msg.attribution_data = failure.attribution_data;
513522
}, ||{}, true, Some(UPDATE|7),
514523
Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false }),
515524
Some(short_channel_id), Some(next_hop_failure.clone()));
@@ -520,8 +529,9 @@ fn test_onion_failure() {
520529
}, |msg| {
521530
let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
522531
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
523-
let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|8, &[0;0]);
532+
let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|8, &[0;0], 0);
524533
msg.reason = failure.data;
534+
msg.attribution_data = failure.attribution_data;
525535
// short_channel_id from the processing node
526536
}, ||{}, true, Some(PERM|8), Some(NetworkUpdate::ChannelFailure{short_channel_id, is_permanent: true}), Some(short_channel_id), Some(next_hop_failure.clone()));
527537

@@ -531,8 +541,9 @@ fn test_onion_failure() {
531541
}, |msg| {
532542
let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
533543
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
534-
let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|9, &[0;0]);
544+
let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|9, &[0;0], 0);
535545
msg.reason = failure.data;
546+
msg.attribution_data = failure.attribution_data;
536547
// short_channel_id from the processing node
537548
}, ||{}, true, Some(PERM|9), Some(NetworkUpdate::ChannelFailure{short_channel_id, is_permanent: true}), Some(short_channel_id), Some(next_hop_failure.clone()));
538549

@@ -664,8 +675,9 @@ fn test_onion_failure() {
664675
// Tamper returning error message
665676
let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
666677
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
667-
let failure = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), 23, &[0;0]);
678+
let failure = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), 23, &[0;0], 0);
668679
msg.reason = failure.data;
680+
msg.attribution_data = failure.attribution_data;
669681
}, ||{
670682
nodes[2].node.fail_htlc_backwards(&payment_hash);
671683
}, true, Some(23), None, None, None);
@@ -685,11 +697,13 @@ fn test_onion_failure() {
685697
decoded_err_packet.hmac = Hmac::from_engine(hmac).to_byte_array();
686698
let mut onion_error = OnionErrorPacket {
687699
data: decoded_err_packet.encode(),
688-
attribution_data: None,
700+
attribution_data: Some(AttributionData::new()),
689701
};
702+
onion_error.attribution_data.as_mut().unwrap().add_hmacs(&onion_keys[1].shared_secret.as_ref(), &onion_error.data);
690703
onion_utils::test_crypt_failure_packet(
691704
&onion_keys[1].shared_secret.as_ref(), &mut onion_error);
692705
msg.reason = onion_error.data;
706+
msg.attribution_data = onion_error.attribution_data;
693707
}, || nodes[2].node.fail_htlc_backwards(&payment_hash), false, None,
694708
Some(NetworkUpdate::NodeFailure { node_id: route.paths[0].hops[1].pubkey, is_permanent: true }),
695709
Some(channels[1].0.contents.short_channel_id), None);
@@ -720,11 +734,13 @@ fn test_onion_failure() {
720734
decoded_err_packet.hmac = Hmac::from_engine(hmac).to_byte_array();
721735
let mut onion_error = OnionErrorPacket{
722736
data: decoded_err_packet.encode(),
723-
attribution_data: None,
737+
attribution_data: Some(AttributionData::new()),
724738
};
739+
onion_error.attribution_data.as_mut().unwrap().add_hmacs(&onion_keys[0].shared_secret.as_ref(), &onion_error.data);
725740
onion_utils::test_crypt_failure_packet(
726741
&onion_keys[0].shared_secret.as_ref(), &mut onion_error);
727742
msg.reason = onion_error.data;
743+
msg.attribution_data = onion_error.attribution_data;
728744
}, || {}, true, Some(0x1000|7),
729745
Some(NetworkUpdate::ChannelFailure {
730746
short_channel_id: channels[1].0.contents.short_channel_id,
@@ -749,11 +765,13 @@ fn test_onion_failure() {
749765
decoded_err_packet.hmac = Hmac::from_engine(hmac).to_byte_array();
750766
let mut onion_error = OnionErrorPacket{
751767
data: decoded_err_packet.encode(),
752-
attribution_data: None,
768+
attribution_data: Some(AttributionData::new()),
753769
};
770+
onion_error.attribution_data.as_mut().unwrap().add_hmacs(&onion_keys[1].shared_secret.as_ref(), &onion_error.data);
754771
onion_utils::test_crypt_failure_packet(
755772
&onion_keys[1].shared_secret.as_ref(), &mut onion_error);
756773
msg.reason = onion_error.data;
774+
msg.attribution_data = onion_error.attribution_data;
757775
}, || nodes[2].node.fail_htlc_backwards(&payment_hash), true, Some(0x1000|7),
758776
Some(NetworkUpdate::ChannelFailure {
759777
short_channel_id: channels[1].0.contents.short_channel_id,

0 commit comments

Comments
 (0)