Skip to content

Commit 42ab42a

Browse files
authored
Merge pull request #3709 from joostjager/fix-attr-failure-crash
Fix long route failure attribution
2 parents 8ee85bf + 42710b5 commit 42ab42a

File tree

1 file changed

+84
-5
lines changed

1 file changed

+84
-5
lines changed

lightning/src/ln/onion_utils.rs

+84-5
Original file line numberDiff line numberDiff line change
@@ -1144,6 +1144,10 @@ where
11441144
.expect("Route we used spontaneously grew invalid keys in the middle of it?");
11451145
}
11461146

1147+
// In the best case, paths can be up to 27 hops. But attribution data can only be conveyed back to the sender from
1148+
// the first 20 hops. Determine the number of hops to be used for attribution data.
1149+
let attributable_hop_count = usize::min(path.hops.len(), MAX_HOPS);
1150+
11471151
// Handle packed channel/node updates for passing back for the route handler
11481152
let mut iterator = onion_keys.into_iter().enumerate().peekable();
11491153
while let Some((route_hop_idx, (route_hop_option, shared_secret))) = iterator.next() {
@@ -1215,11 +1219,13 @@ where
12151219
// Check attr error HMACs if present.
12161220
if let Some(ref mut attribution_data) = encrypted_packet.attribution_data {
12171221
// Only consider hops in the regular path for attribution data. Failures in a blinded path are not attributable.
1218-
if route_hop_idx < path.hops.len() {
1219-
// Calculate position relative to the final hop. The final hop is at position 0. The failure node does not
1220-
// need to come from the final node, but we need to look at the chain of HMACs that does include all data up
1221-
// to the final node. For a more nearby failure, the verified HMACs will include some zero padding data.
1222-
let position = path.hops.len() - route_hop_idx - 1;
1222+
if route_hop_idx < attributable_hop_count {
1223+
// Calculate position relative to the last attributable hop. The last attributable hop is at position 0.
1224+
// The failure node does not need to come from the last attributable hop, but we need to look at the
1225+
// chain of HMACs that does include all data up to the last attributable hop. For a more nearby failure,
1226+
// the verified HMACs will include some zero padding data. Failures beyond the last attributable hop
1227+
// will not be attributable.
1228+
let position = attributable_hop_count - route_hop_idx - 1;
12231229
let hold_time = attribution_data.verify(
12241230
&encrypted_packet.data,
12251231
shared_secret.as_ref(),
@@ -3169,6 +3175,79 @@ mod tests {
31693175
assert_eq!(decrypted_failure.short_channel_id, Some(0));
31703176
}
31713177

3178+
#[test]
3179+
fn test_long_route_attributable_failure() {
3180+
// Test a long route that exceeds the reach of attribution data.
3181+
3182+
let secp_ctx = Secp256k1::new();
3183+
const LEGACY_MAX_HOPS: usize = 27;
3184+
3185+
// Construct a route with 27 hops.
3186+
let mut hops = Vec::new();
3187+
for i in 0..LEGACY_MAX_HOPS {
3188+
let mut secret_bytes = [0; 32];
3189+
secret_bytes[0] = (i + 1) as u8;
3190+
let secret_key = SecretKey::from_slice(&secret_bytes).unwrap();
3191+
let pubkey = secret_key.public_key(&secp_ctx);
3192+
3193+
hops.push(RouteHop {
3194+
pubkey,
3195+
channel_features: ChannelFeatures::empty(),
3196+
node_features: NodeFeatures::empty(),
3197+
short_channel_id: i as u64,
3198+
fee_msat: 0,
3199+
cltv_expiry_delta: 0,
3200+
maybe_announced_channel: true,
3201+
});
3202+
}
3203+
let path = Path { hops, blinded_tail: None };
3204+
3205+
// Calculate shared secrets.
3206+
let mut onion_keys = Vec::new();
3207+
let session_key = get_test_session_key();
3208+
construct_onion_keys_generic_callback(
3209+
&secp_ctx,
3210+
&path.hops,
3211+
None,
3212+
&session_key,
3213+
|shared_secret, _, _, _, _| onion_keys.push(shared_secret),
3214+
)
3215+
.unwrap();
3216+
3217+
// Construct the htlc source.
3218+
let logger = TestLogger::new();
3219+
let htlc_source = HTLCSource::OutboundRoute {
3220+
path,
3221+
session_priv: session_key,
3222+
first_hop_htlc_msat: 0,
3223+
payment_id: PaymentId([1; 32]),
3224+
};
3225+
3226+
// Iterate over all possible failure positions and check that the cases that can be attributed are.
3227+
for failure_pos in 0..LEGACY_MAX_HOPS {
3228+
// Create a failure packet with bogus data.
3229+
let packet = vec![1u8; 292];
3230+
let mut onion_error =
3231+
OnionErrorPacket { data: packet, attribution_data: Some(AttributionData::new()) };
3232+
3233+
// Apply the processing that the preceding hops would apply.
3234+
for i in (0..failure_pos).rev() {
3235+
let shared_secret = onion_keys[i].secret_bytes();
3236+
process_failure_packet(&mut onion_error, &shared_secret, 0);
3237+
super::crypt_failure_packet(&shared_secret, &mut onion_error);
3238+
}
3239+
3240+
// Decrypt the failure.
3241+
let decrypted_failure =
3242+
process_onion_failure(&secp_ctx, &&logger, &htlc_source, onion_error);
3243+
3244+
// Expect attribution up to hop 20.
3245+
let expected_failed_chan =
3246+
if failure_pos < MAX_HOPS { Some(failure_pos as u64) } else { None };
3247+
assert_eq!(decrypted_failure.short_channel_id, expected_failed_chan);
3248+
}
3249+
}
3250+
31723251
#[test]
31733252
fn test_unreadable_failure_packet_onion() {
31743253
// Create a failure packet with a valid hmac but unreadable failure message.

0 commit comments

Comments
 (0)