Skip to content

Commit 3da1c88

Browse files
committed
Handle Trampoline hops in error decryption
Rather than solely iterating over `RouteHop`s, we now also append the shared secrets from the inner onion containing `TrampolineHop`s.
1 parent 7ded4c3 commit 3da1c88

File tree

2 files changed

+47
-11
lines changed

2 files changed

+47
-11
lines changed

lightning/src/ln/onion_utils.rs

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -967,7 +967,7 @@ where
967967
let mut htlc_msat = *first_hop_htlc_msat;
968968
let mut _error_code_ret = None;
969969
let mut _error_packet_ret = None;
970-
let mut is_from_final_node = false;
970+
let mut is_from_final_non_blinded_node = false;
971971

972972
const BADONION: u16 = 0x8000;
973973
const PERM: u16 = 0x4000;
@@ -976,41 +976,72 @@ where
976976

977977
enum ErrorHop<'a> {
978978
RouteHop(&'a RouteHop),
979+
TrampolineHop(&'a TrampolineHop),
979980
}
980981

981982
impl<'a> ErrorHop<'a> {
982983
fn fee_msat(&self) -> u64 {
983984
match self {
984985
ErrorHop::RouteHop(rh) => rh.fee_msat,
986+
ErrorHop::TrampolineHop(th) => th.fee_msat,
985987
}
986988
}
987989

988990
fn pubkey(&self) -> &PublicKey {
989991
match self {
990992
ErrorHop::RouteHop(rh) => rh.node_pubkey(),
993+
ErrorHop::TrampolineHop(th) => th.node_pubkey(),
991994
}
992995
}
993996

994997
fn short_channel_id(&self) -> Option<u64> {
995998
match self {
996999
ErrorHop::RouteHop(rh) => Some(rh.short_channel_id),
1000+
ErrorHop::TrampolineHop(_) => None,
9971001
}
9981002
}
9991003
}
10001004

1005+
let outer_session_priv = path.has_trampoline_hops().then(|| {
1006+
// if we have Trampoline hops, the outer onion session_priv is a hash of the inner one
1007+
let session_priv_hash = Sha256::hash(&session_priv.secret_bytes()).to_byte_array();
1008+
SecretKey::from_slice(&session_priv_hash[..]).expect("You broke SHA-256!")
1009+
});
1010+
10011011
let num_blinded_hops = path.blinded_tail.as_ref().map_or(0, |bt| bt.hops.len());
1012+
1013+
// We are first collecting all the unblinded `RouteHop`s inside `onion_keys`. Then, if applicable,
1014+
// we will add all the `TrampolineHop`s, and finally, the blinded hops.
10021015
let mut onion_keys = Vec::with_capacity(path.hops.len() + num_blinded_hops);
10031016
construct_onion_keys_generic_callback(
10041017
secp_ctx,
10051018
&path.hops,
1006-
path.blinded_tail.as_ref(),
1007-
session_priv,
1019+
// if we have Trampoline hops, the blinded hops are part of the inner Trampoline onion
1020+
if path.has_trampoline_hops() { None } else { path.blinded_tail.as_ref() },
1021+
outer_session_priv.as_ref().unwrap_or(session_priv),
10081022
|shared_secret, _, _, route_hop_option: Option<&RouteHop>, _| {
10091023
onion_keys.push((route_hop_option.map(|rh| ErrorHop::RouteHop(rh)), shared_secret))
10101024
},
10111025
)
10121026
.expect("Route we used spontaneously grew invalid keys in the middle of it?");
10131027

1028+
if path.has_trampoline_hops() {
1029+
construct_onion_keys_generic_callback(
1030+
secp_ctx,
1031+
// Trampoline hops are part of the blinded tail, so this can never panic
1032+
&path.blinded_tail.as_ref().unwrap().trampoline_hops,
1033+
path.blinded_tail.as_ref(),
1034+
session_priv,
1035+
|shared_secret, _, _, trampoline_hop_option: Option<&TrampolineHop>, _| {
1036+
onion_keys.push((
1037+
trampoline_hop_option.map(|th| ErrorHop::TrampolineHop(th)),
1038+
shared_secret,
1039+
))
1040+
},
1041+
)
1042+
.expect("Route we used spontaneously grew invalid keys in the middle of it?");
1043+
}
1044+
10141045
// Handle packed channel/node updates for passing back for the route handler
10151046
let mut iterator = onion_keys.into_iter().peekable();
10161047
while let Some((route_hop_option, shared_secret)) = iterator.next() {
@@ -1032,12 +1063,12 @@ where
10321063

10331064
// The failing hop includes either the inbound channel to the recipient or the outbound channel
10341065
// from the current hop (i.e., the next hop's inbound channel).
1035-
// For 1-hop blinded paths, the final `path.hops` entry is the recipient.
1066+
// For 1-hop blinded paths, the final `ErrorHop` entry is the recipient.
10361067
// In our case that means that if we're on the last iteration, and there is no more than one
10371068
// blinded hop, the current iteration references the last non-blinded hop.
10381069
let next_hop = iterator.peek();
1039-
is_from_final_node = next_hop.is_none() && num_blinded_hops <= 1;
1040-
let failing_route_hop = if is_from_final_node {
1070+
is_from_final_non_blinded_node = next_hop.is_none() && num_blinded_hops <= 1;
1071+
let failing_route_hop = if is_from_final_non_blinded_node {
10411072
route_hop
10421073
} else {
10431074
match next_hop {
@@ -1102,7 +1133,7 @@ where
11021133
res = Some(FailureLearnings {
11031134
network_update,
11041135
short_channel_id,
1105-
payment_failed_permanently: is_from_final_node,
1136+
payment_failed_permanently: is_from_final_non_blinded_node,
11061137
failed_within_blinded_path: false,
11071138
});
11081139
break;
@@ -1124,7 +1155,7 @@ where
11241155
res = Some(FailureLearnings {
11251156
network_update,
11261157
short_channel_id,
1127-
payment_failed_permanently: is_from_final_node,
1158+
payment_failed_permanently: is_from_final_non_blinded_node,
11281159
failed_within_blinded_path: false,
11291160
});
11301161
break;
@@ -1141,7 +1172,7 @@ where
11411172
let payment_failed = match error_code & 0xff {
11421173
15 | 16 | 17 | 18 | 19 | 23 => true,
11431174
_ => false,
1144-
} && is_from_final_node; // PERM bit observed below even if this error is from the intermediate nodes
1175+
} && is_from_final_non_blinded_node; // PERM bit observed below even if this error is from the intermediate nodes
11451176

11461177
let mut network_update = None;
11471178
let mut short_channel_id = None;
@@ -1226,7 +1257,7 @@ where
12261257
res = Some(FailureLearnings {
12271258
network_update,
12281259
short_channel_id,
1229-
payment_failed_permanently: error_code & PERM == PERM && is_from_final_node,
1260+
payment_failed_permanently: error_code & PERM == PERM && is_from_final_non_blinded_node,
12301261
failed_within_blinded_path: false,
12311262
});
12321263

@@ -1285,7 +1316,7 @@ where
12851316
DecodedOnionFailure {
12861317
network_update: None,
12871318
short_channel_id: None,
1288-
payment_failed_permanently: is_from_final_node,
1319+
payment_failed_permanently: is_from_final_non_blinded_node,
12891320
failed_within_blinded_path: false,
12901321
#[cfg(any(test, feature = "_test_utils"))]
12911322
onion_error_code: None,

lightning/src/routing/router.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,11 @@ impl Path {
519519
None => self.hops.last().map(|hop| hop.cltv_expiry_delta)
520520
}
521521
}
522+
523+
/// True if this [`Path`] has at least one Trampoline hop.
524+
pub fn has_trampoline_hops(&self) -> bool {
525+
self.blinded_tail.as_ref().map_or(false, |bt| !bt.trampoline_hops.is_empty())
526+
}
522527
}
523528

524529
/// A route directs a payment from the sender (us) to the recipient. If the recipient supports MPP,

0 commit comments

Comments
 (0)