Skip to content

Commit ca438fb

Browse files
committed
Introduce Payment Dummy Hop parsing mechanism
1 parent 2319bfe commit ca438fb

File tree

4 files changed

+190
-37
lines changed

4 files changed

+190
-37
lines changed

lightning/src/blinded_path/payment.rs

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ use crate::util::ser::{
3434
Writeable, Writer,
3535
};
3636

37-
use core::mem;
3837
use core::ops::Deref;
3938

4039
#[allow(unused_imports)]
@@ -219,28 +218,31 @@ impl BlindedPaymentPath {
219218
NL::Target: NodeIdLookUp,
220219
T: secp256k1::Signing + secp256k1::Verification,
221220
{
222-
match self.decrypt_intro_payload::<NS>(node_signer) {
223-
Ok((
224-
BlindedPaymentTlvs::Forward(ForwardTlvs { short_channel_id, .. }),
225-
control_tlvs_ss,
226-
)) => {
227-
let next_node_id = match node_id_lookup.next_node_id(short_channel_id) {
228-
Some(node_id) => node_id,
229-
None => return Err(()),
230-
};
231-
let mut new_blinding_point = onion_utils::next_hop_pubkey(
232-
secp_ctx,
233-
self.inner_path.blinding_point,
234-
control_tlvs_ss.as_ref(),
235-
)
236-
.map_err(|_| ())?;
237-
mem::swap(&mut self.inner_path.blinding_point, &mut new_blinding_point);
238-
self.inner_path.introduction_node = IntroductionNode::NodeId(next_node_id);
239-
self.inner_path.blinded_hops.remove(0);
240-
Ok(())
241-
},
242-
_ => Err(()),
243-
}
221+
let (next_node_id, control_tlvs_ss) =
222+
match self.decrypt_intro_payload::<NS>(node_signer).map_err(|_| ())? {
223+
(BlindedPaymentTlvs::Forward(ForwardTlvs { short_channel_id, .. }), ss) => {
224+
let node_id = node_id_lookup.next_node_id(short_channel_id).ok_or(())?;
225+
(node_id, ss)
226+
},
227+
(BlindedPaymentTlvs::Dummy, ss) => {
228+
let node_id = node_signer.get_node_id(Recipient::Node)?;
229+
(node_id, ss)
230+
},
231+
_ => return Err(()),
232+
};
233+
234+
let new_blinding_point = onion_utils::next_hop_pubkey(
235+
secp_ctx,
236+
self.inner_path.blinding_point,
237+
control_tlvs_ss.as_ref(),
238+
)
239+
.map_err(|_| ())?;
240+
241+
self.inner_path.blinding_point = new_blinding_point;
242+
self.inner_path.introduction_node = IntroductionNode::NodeId(next_node_id);
243+
self.inner_path.blinded_hops.remove(0);
244+
245+
Ok(())
244246
}
245247

246248
pub(crate) fn decrypt_intro_payload<NS: Deref>(
@@ -262,9 +264,9 @@ impl BlindedPaymentPath {
262264
.map_err(|_| ())?;
263265

264266
match (&readable, used_aad) {
265-
(BlindedPaymentTlvs::Forward(_), false) | (BlindedPaymentTlvs::Receive(_), true) => {
266-
Ok((readable, control_tlvs_ss))
267-
},
267+
(BlindedPaymentTlvs::Forward(_), false)
268+
| (BlindedPaymentTlvs::Dummy, true)
269+
| (BlindedPaymentTlvs::Receive(_), true) => Ok((readable, control_tlvs_ss)),
268270
_ => Err(()),
269271
}
270272
}

lightning/src/ln/channelmanager.rs

Lines changed: 119 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ use crate::ln::channel_state::ChannelDetails;
6666
use crate::ln::funding::SpliceContribution;
6767
use crate::ln::inbound_payment;
6868
use crate::ln::interactivetxs::InteractiveTxMessageSend;
69-
use crate::ln::msgs;
69+
use crate::ln::msgs::{self, OnionPacket, UpdateAddHTLC};
7070
use crate::ln::msgs::{
7171
BaseMessageHandler, ChannelMessageHandler, CommitmentUpdate, DecodeError, LightningError,
7272
MessageSendEvent,
@@ -229,6 +229,22 @@ pub enum PendingHTLCRouting {
229229
/// [`ReleaseHeldHtlc`] onion message.
230230
hold_htlc: Option<()>,
231231
},
232+
/// A dummy HTLC hop which does not represent a real routing decision.
233+
///
234+
/// Dummy HTLCs are introduced to pad a blinded path. When such an HTLC is received, the dummy
235+
/// layer is peeled, producing a new onion packet which must be processed again. This process
236+
/// repeats until a non-dummy routing decision is reached, which is guaranteed to be
237+
/// [`PendingHTLCRouting::Receive`].
238+
Dummy {
239+
/// The onion packet obtained after removing the dummy layer.
240+
///
241+
/// This packet must be re-processed as if it were freshly received.
242+
onion_packet: msgs::OnionPacket,
243+
/// Set if this HTLC is being forwarded within a blinded path.
244+
blinded: Option<BlindedForward>,
245+
/// The absolute CLTV of the inbound HTLC.
246+
incoming_cltv_expiry: Option<u32>,
247+
},
232248
/// An HTLC which should be forwarded on to another Trampoline node.
233249
TrampolineForward {
234250
/// The onion shared secret we build with the sender (or the preceding Trampoline node) used
@@ -352,6 +368,7 @@ impl PendingHTLCRouting {
352368
fn blinded_failure(&self) -> Option<BlindedFailure> {
353369
match self {
354370
Self::Forward { blinded: Some(BlindedForward { failure, .. }), .. } => Some(*failure),
371+
Self::Dummy { blinded: Some(BlindedForward { failure, .. }), .. } => Some(*failure),
355372
Self::TrampolineForward { blinded: Some(BlindedForward { failure, .. }), .. } => {
356373
Some(*failure)
357374
},
@@ -368,6 +385,7 @@ impl PendingHTLCRouting {
368385
fn incoming_cltv_expiry(&self) -> Option<u32> {
369386
match self {
370387
Self::Forward { incoming_cltv_expiry, .. } => *incoming_cltv_expiry,
388+
Self::Dummy { incoming_cltv_expiry, .. } => *incoming_cltv_expiry,
371389
Self::TrampolineForward { incoming_cltv_expiry, .. } => Some(*incoming_cltv_expiry),
372390
Self::Receive { incoming_cltv_expiry, .. } => Some(*incoming_cltv_expiry),
373391
Self::ReceiveKeysend { incoming_cltv_expiry, .. } => Some(*incoming_cltv_expiry),
@@ -4974,6 +4992,11 @@ where
49744992
) -> Result<(), LocalHTLCFailureReason> {
49754993
let outgoing_scid = match next_packet_details.outgoing_connector {
49764994
HopConnector::ShortChannelId(scid) => scid,
4995+
HopConnector::Dummy => {
4996+
// Dummy hops are only used for path padding and must not reach HTLC processing.
4997+
debug_assert!(false, "Dummy hop reached HTLC handling.");
4998+
return Err(LocalHTLCFailureReason::InvalidOnionPayload);
4999+
}
49775000
HopConnector::Trampoline(_) => {
49785001
return Err(LocalHTLCFailureReason::InvalidTrampolineForward);
49795002
}
@@ -6878,6 +6901,7 @@ where
68786901
fn process_pending_update_add_htlcs(&self) -> bool {
68796902
let mut should_persist = false;
68806903
let mut decode_update_add_htlcs = new_hash_map();
6904+
let mut dummy_update_add_htlcs = new_hash_map();
68816905
mem::swap(&mut decode_update_add_htlcs, &mut self.decode_update_add_htlcs.lock().unwrap());
68826906

68836907
let get_htlc_failure_type = |outgoing_scid_opt: Option<u64>, payment_hash: PaymentHash| {
@@ -6941,7 +6965,68 @@ where
69416965
&*self.logger,
69426966
&self.secp_ctx,
69436967
) {
6944-
Ok(decoded_onion) => decoded_onion,
6968+
Ok(decoded_onion) => match decoded_onion {
6969+
(
6970+
onion_utils::Hop::Dummy {
6971+
intro_node_blinding_point,
6972+
next_hop_hmac,
6973+
new_packet_bytes,
6974+
..
6975+
},
6976+
Some(NextPacketDetails {
6977+
next_packet_pubkey,
6978+
outgoing_connector,
6979+
..
6980+
}),
6981+
) => {
6982+
debug_assert!(
6983+
matches!(outgoing_connector, HopConnector::Dummy),
6984+
"Dummy hop must always map to HopConnector::Dummy"
6985+
);
6986+
6987+
// Dummy hops are not forwarded. Instead, we reconstruct a new UpdateAddHTLC
6988+
// with the next onion packet (ephemeral pubkey, hop data, and HMAC) and push
6989+
// it back into our own processing queue. This lets us step through the dummy
6990+
// layers locally until we reach the next real hop.
6991+
let next_blinding_point = intro_node_blinding_point
6992+
.or(update_add_htlc.blinding_point)
6993+
.and_then(|blinding_point| {
6994+
let ss = self
6995+
.node_signer
6996+
.ecdh(Recipient::Node, &blinding_point, None)
6997+
.ok()?
6998+
.secret_bytes();
6999+
7000+
onion_utils::next_hop_pubkey(
7001+
&self.secp_ctx,
7002+
blinding_point,
7003+
&ss,
7004+
)
7005+
.ok()
7006+
});
7007+
7008+
let new_onion_packet = OnionPacket {
7009+
version: 0,
7010+
public_key: next_packet_pubkey,
7011+
hop_data: new_packet_bytes,
7012+
hmac: next_hop_hmac,
7013+
};
7014+
7015+
let new_update_add_htlc = UpdateAddHTLC {
7016+
onion_routing_packet: new_onion_packet,
7017+
blinding_point: next_blinding_point,
7018+
..update_add_htlc.clone()
7019+
};
7020+
7021+
dummy_update_add_htlcs
7022+
.entry(incoming_scid_alias)
7023+
.or_insert_with(Vec::new)
7024+
.push(new_update_add_htlc);
7025+
7026+
continue;
7027+
},
7028+
_ => decoded_onion,
7029+
},
69457030

69467031
Err((htlc_fail, reason)) => {
69477032
let failure_type = HTLCHandlingFailureType::InvalidOnion;
@@ -6954,6 +7039,13 @@ where
69547039
let outgoing_scid_opt =
69557040
next_packet_details_opt.as_ref().and_then(|d| match d.outgoing_connector {
69567041
HopConnector::ShortChannelId(scid) => Some(scid),
7042+
HopConnector::Dummy => {
7043+
debug_assert!(
7044+
false,
7045+
"Dummy hops must never be processed at this stage."
7046+
);
7047+
None
7048+
},
69577049
HopConnector::Trampoline(_) => None,
69587050
});
69597051
let shared_secret = next_hop.shared_secret().secret_bytes();
@@ -7097,6 +7189,19 @@ where
70977189
));
70987190
}
70997191
}
7192+
7193+
// Merge peeled dummy HTLCs into the existing decode queue so they can be
7194+
// processed in the next iteration. We avoid replacing the whole queue
7195+
// (e.g. via mem::swap) because other threads may have enqueued new HTLCs
7196+
// meanwhile; merging preserves everything safely.
7197+
if !dummy_update_add_htlcs.is_empty() {
7198+
let mut decode_update_add_htlc_source = self.decode_update_add_htlcs.lock().unwrap();
7199+
7200+
for (incoming_scid_alias, htlcs) in dummy_update_add_htlcs.into_iter() {
7201+
decode_update_add_htlc_source.entry(incoming_scid_alias).or_default().extend(htlcs);
7202+
}
7203+
}
7204+
71007205
should_persist
71017206
}
71027207

@@ -11419,6 +11524,13 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
1141911524
for (forward_info, prev_htlc_id) in pending_forwards.drain(..) {
1142011525
let scid = match forward_info.routing {
1142111526
PendingHTLCRouting::Forward { short_channel_id, .. } => short_channel_id,
11527+
PendingHTLCRouting::Dummy { .. } => {
11528+
debug_assert!(
11529+
false,
11530+
"Dummy hops must never be processed at this stage."
11531+
);
11532+
0
11533+
},
1142211534
PendingHTLCRouting::TrampolineForward { .. } => 0,
1142311535
PendingHTLCRouting::Receive { .. } => 0,
1142411536
PendingHTLCRouting::ReceiveKeysend { .. } => 0,
@@ -15966,6 +16078,11 @@ impl_writeable_tlv_based_enum!(PendingHTLCRouting,
1596616078
(4, blinded, option),
1596716079
(6, node_id, required),
1596816080
(8, incoming_cltv_expiry, required),
16081+
},
16082+
(4, Dummy) => {
16083+
(0, onion_packet, required),
16084+
(1, blinded, option),
16085+
(2, incoming_cltv_expiry, option),
1596916086
}
1597016087
);
1597116088

lightning/src/ln/onion_payment.rs

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@ enum RoutingInfo {
106106
new_packet_bytes: [u8; ONION_DATA_LEN],
107107
next_hop_hmac: [u8; 32],
108108
},
109+
Dummy {
110+
new_packet_bytes: [u8; ONION_DATA_LEN],
111+
next_hop_hmac: [u8; 32],
112+
},
109113
Trampoline {
110114
next_trampoline: PublicKey,
111115
// Trampoline onions are currently variable length
@@ -149,13 +153,8 @@ pub(super) fn create_fwd_pending_htlc_info(
149153
(RoutingInfo::Direct { short_channel_id, new_packet_bytes, next_hop_hmac }, amt_to_forward, outgoing_cltv_value, intro_node_blinding_point,
150154
next_blinding_override)
151155
},
152-
onion_utils::Hop::Dummy { .. } => {
153-
debug_assert!(false, "Dummy hop should have been peeled earlier");
154-
return Err(InboundHTLCErr {
155-
msg: "Dummy Hop OnionHopData provided for us as an intermediary node",
156-
reason: LocalHTLCFailureReason::InvalidOnionPayload,
157-
err_data: Vec::new(),
158-
})
156+
onion_utils::Hop::Dummy { intro_node_blinding_point, next_hop_hmac, new_packet_bytes, .. } => {
157+
(RoutingInfo::Dummy { new_packet_bytes, next_hop_hmac }, msg.amount_msat, msg.cltv_expiry, intro_node_blinding_point, None)
159158
},
160159
onion_utils::Hop::Receive { .. } | onion_utils::Hop::BlindedReceive { .. } =>
161160
return Err(InboundHTLCErr {
@@ -234,7 +233,28 @@ pub(super) fn create_fwd_pending_htlc_info(
234233
.unwrap_or(BlindedFailure::FromBlindedNode),
235234
}),
236235
}
237-
}
236+
},
237+
RoutingInfo::Dummy { new_packet_bytes, next_hop_hmac } => {
238+
let outgoing_packet = msgs::OnionPacket {
239+
version: 0,
240+
public_key: next_packet_pubkey_opt.unwrap_or(Err(secp256k1::Error::InvalidPublicKey)),
241+
hop_data: new_packet_bytes,
242+
hmac: next_hop_hmac,
243+
};
244+
245+
PendingHTLCRouting::Dummy {
246+
onion_packet: outgoing_packet,
247+
incoming_cltv_expiry: Some(msg.cltv_expiry),
248+
blinded: intro_node_blinding_point.or(msg.blinding_point)
249+
.map(|bp| BlindedForward {
250+
inbound_blinding_point: bp,
251+
next_blinding_override: None,
252+
failure: intro_node_blinding_point
253+
.map(|_| BlindedFailure::FromIntroductionNode)
254+
.unwrap_or(BlindedFailure::FromBlindedNode),
255+
}),
256+
}
257+
},
238258
RoutingInfo::Trampoline { next_trampoline, new_packet_bytes, next_hop_hmac, shared_secret, current_path_key } => {
239259
let next_trampoline_packet_pubkey = match next_packet_pubkey_opt {
240260
Some(Ok(pubkey)) => pubkey,
@@ -545,6 +565,8 @@ where
545565
pub(super) enum HopConnector {
546566
// scid-based routing
547567
ShortChannelId(u64),
568+
// Dummy hop for path padding
569+
Dummy,
548570
// Trampoline-based routing
549571
#[allow(unused)]
550572
Trampoline(PublicKey),
@@ -648,7 +670,13 @@ where
648670
next_packet_pubkey, outgoing_connector: HopConnector::ShortChannelId(short_channel_id), outgoing_amt_msat: amt_to_forward,
649671
outgoing_cltv_value
650672
})
651-
}
673+
},
674+
onion_utils::Hop::Dummy { shared_secret, .. } => {
675+
let next_packet_pubkey = onion_utils::next_hop_pubkey(secp_ctx,
676+
msg.onion_routing_packet.public_key.unwrap(), &shared_secret.secret_bytes());
677+
678+
Some(NextPacketDetails { next_packet_pubkey, outgoing_connector: HopConnector::Dummy, outgoing_amt_msat: msg.amount_msat, outgoing_cltv_value: msg.cltv_expiry })
679+
},
652680
onion_utils::Hop::TrampolineForward { next_trampoline_hop_data: msgs::InboundTrampolineForwardPayload { amt_to_forward, outgoing_cltv_value, next_trampoline }, trampoline_shared_secret, incoming_trampoline_public_key, .. } => {
653681
let next_trampoline_packet_pubkey = onion_utils::next_hop_pubkey(secp_ctx,
654682
incoming_trampoline_public_key, &trampoline_shared_secret.secret_bytes());

lightning/src/ln/onion_utils.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2356,6 +2356,12 @@ where
23562356
new_packet_bytes,
23572357
})
23582358
},
2359+
msgs::InboundOnionPayload::Dummy { intro_node_blinding_point } => Ok(Hop::Dummy {
2360+
intro_node_blinding_point,
2361+
shared_secret,
2362+
next_hop_hmac,
2363+
new_packet_bytes,
2364+
}),
23592365
_ => {
23602366
if blinding_point.is_some() {
23612367
return Err(OnionDecodeErr::Malformed {

0 commit comments

Comments
 (0)