Skip to content

Commit 8d8313d

Browse files
committed
Correct blinded path forwarding CLTV expiry check
The `PaymentConstraints::max_cltv_expiry` field exists to ensure a blinded path expires across the entire path at once - once the path is expired it will be rejected by the introduction node rather than traversing the entire path and failing at the destination. This was broken by the fact that we were checking the outgoing CLTV value rather than the incoming one, which admittedly isn't clear in the spec but is somewhat implied. Here we fix this, updating a test which was actually (kinda) exploiting this privacy loss rather than allowing the HTLC to fail at the introduction node. This, of course, does not risk funds loss as our own CLTV policy is still enforced on top. The only impact it could have is a recipient which was relying on blinded path expiry to avoid some cost (e.g. LSPS5 node wakeup cost) involved in receiving an HTLC they ultimately fail, though I'm not aware of any practical deployment where that is a concern. Reported by Jordan Mecom of Block's Security Team
1 parent 9f73a98 commit 8d8313d

2 files changed

Lines changed: 14 additions & 15 deletions

File tree

lightning/src/ln/async_payments_tests.rs

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1886,8 +1886,9 @@ fn expired_static_invoice_payment_path() {
18861886
}
18871887
};
18881888

1889-
// Mine a bunch of blocks so the hardcoded path's `max_cltv_expiry` is expired at the recipient's
1890-
// end by the time the payment arrives.
1889+
// Mine a bunch of blocks on the sender so the hardcoded path's `max_cltv_expiry` is expired.
1890+
// Note that the path expires "all at once" and will be invalid at the intro point so will be
1891+
// rejected before it reaches the destination.
18911892
let min_cltv_expiry_delta = test_default_channel_config().channel_config.cltv_expiry_delta;
18921893
connect_blocks(
18931894
&nodes[0],
@@ -1902,7 +1903,6 @@ fn expired_static_invoice_payment_path() {
19021903
&nodes[1],
19031904
final_max_cltv_expiry
19041905
- nodes[1].best_block_info().1
1905-
// Don't expire the path for nodes[1]
19061906
- min_cltv_expiry_delta as u32
19071907
- HTLC_FAIL_BACK_BUFFER
19081908
- LATENCY_GRACE_PERIOD_BLOCKS
@@ -1939,18 +1939,17 @@ fn expired_static_invoice_payment_path() {
19391939
let payment_hash = extract_payment_hash(&ev);
19401940
check_added_monitors(&nodes[0], 1);
19411941

1942-
let route: &[&[&Node]] = &[&[&nodes[1], &nodes[2]]];
1943-
let args = PassAlongPathArgs::new(&nodes[0], route[0], amt_msat, payment_hash, ev)
1944-
.without_claimable_event()
1945-
.expect_failure(HTLCHandlingFailureType::Receive { payment_hash })
1946-
.with_dummy_tlvs(&[DummyTlvs::default(); DEFAULT_PAYMENT_DUMMY_HOPS]);
1947-
do_pass_along_path(args);
1948-
fail_blinded_htlc_backwards(payment_hash, 1, &[&nodes[0], &nodes[1], &nodes[2]], false);
1949-
nodes[2].logger.assert_log_contains(
1950-
"lightning::ln::channelmanager",
1951-
"violated blinded payment constraints",
1952-
1,
1942+
let payment_event = SendEvent::from_event(ev);
1943+
nodes[1].node.handle_update_add_htlc(nodes[0].node.get_our_node_id(), &payment_event.msgs[0]);
1944+
check_added_monitors(&nodes[1], 0);
1945+
do_commitment_signed_dance(&nodes[1], &nodes[0], &payment_event.commitment_msg, false, true);
1946+
expect_and_process_pending_htlcs(&nodes[1], false);
1947+
expect_htlc_handling_failed_destinations!(
1948+
nodes[1].node.get_and_clear_pending_events(),
1949+
&[HTLCHandlingFailureType::InvalidOnion]
19531950
);
1951+
check_added_monitors(&nodes[1], 1);
1952+
fail_blinded_htlc_backwards(payment_hash, 1, &[&nodes[0], &nodes[1]], false);
19541953
}
19551954

19561955
#[cfg_attr(feature = "std", ignore)]

lightning/src/ln/onion_payment.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ fn check_blinded_forward(
6666
let outgoing_cltv_value = inbound_cltv_expiry.checked_sub(
6767
payment_relay.cltv_expiry_delta as u32
6868
).ok_or(())?;
69-
check_blinded_payment_constraints(inbound_amt_msat, outgoing_cltv_value, payment_constraints)?;
69+
check_blinded_payment_constraints(inbound_amt_msat, inbound_cltv_expiry, payment_constraints)?;
7070

7171
if features.requires_unknown_bits_from(&BlindedHopFeatures::empty()) { return Err(()) }
7272
Ok((amt_to_forward, outgoing_cltv_value))

0 commit comments

Comments
 (0)