Skip to content

Commit d2def54

Browse files
committed
Introduce Payment Dummy Hop parsing mechanism
1 parent 923949b commit d2def54

File tree

4 files changed

+189
-29
lines changed

4 files changed

+189
-29
lines changed

lightning/src/blinded_path/payment.rs

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

36-
use core::mem;
3736
use core::ops::Deref;
3837

3938
#[allow(unused_imports)]
@@ -248,28 +247,31 @@ impl BlindedPaymentPath {
248247
NL::Target: NodeIdLookUp,
249248
T: secp256k1::Signing + secp256k1::Verification,
250249
{
251-
match self.decrypt_intro_payload::<NS>(node_signer) {
252-
Ok((
253-
BlindedPaymentTlvs::Forward(ForwardTlvs { short_channel_id, .. }),
254-
control_tlvs_ss,
255-
)) => {
256-
let next_node_id = match node_id_lookup.next_node_id(short_channel_id) {
257-
Some(node_id) => node_id,
258-
None => return Err(()),
259-
};
260-
let mut new_blinding_point = onion_utils::next_hop_pubkey(
261-
secp_ctx,
262-
self.inner_path.blinding_point,
263-
control_tlvs_ss.as_ref(),
264-
)
265-
.map_err(|_| ())?;
266-
mem::swap(&mut self.inner_path.blinding_point, &mut new_blinding_point);
267-
self.inner_path.introduction_node = IntroductionNode::NodeId(next_node_id);
268-
self.inner_path.blinded_hops.remove(0);
269-
Ok(())
270-
},
271-
_ => Err(()),
272-
}
250+
let (next_node_id, control_tlvs_ss) =
251+
match self.decrypt_intro_payload::<NS>(node_signer).map_err(|_| ())? {
252+
(BlindedPaymentTlvs::Forward(ForwardTlvs { short_channel_id, .. }), ss) => {
253+
let node_id = node_id_lookup.next_node_id(short_channel_id).ok_or(())?;
254+
(node_id, ss)
255+
},
256+
(BlindedPaymentTlvs::Dummy(_), ss) => {
257+
let node_id = node_signer.get_node_id(Recipient::Node)?;
258+
(node_id, ss)
259+
},
260+
_ => return Err(()),
261+
};
262+
263+
let new_blinding_point = onion_utils::next_hop_pubkey(
264+
secp_ctx,
265+
self.inner_path.blinding_point,
266+
control_tlvs_ss.as_ref(),
267+
)
268+
.map_err(|_| ())?;
269+
270+
self.inner_path.blinding_point = new_blinding_point;
271+
self.inner_path.introduction_node = IntroductionNode::NodeId(next_node_id);
272+
self.inner_path.blinded_hops.remove(0);
273+
274+
Ok(())
273275
}
274276

275277
pub(crate) fn decrypt_intro_payload<NS: Deref>(
@@ -291,9 +293,9 @@ impl BlindedPaymentPath {
291293
.map_err(|_| ())?;
292294

293295
match (&readable, used_aad) {
294-
(BlindedPaymentTlvs::Forward(_), false) | (BlindedPaymentTlvs::Receive(_), true) => {
295-
Ok((readable, control_tlvs_ss))
296-
},
296+
(BlindedPaymentTlvs::Forward(_), false)
297+
| (BlindedPaymentTlvs::Dummy(_), true)
298+
| (BlindedPaymentTlvs::Receive(_), true) => Ok((readable, control_tlvs_ss)),
297299
_ => Err(()),
298300
}
299301
}

lightning/src/ln/channelmanager.rs

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4974,6 +4974,11 @@ where
49744974
) -> Result<(), LocalHTLCFailureReason> {
49754975
let outgoing_scid = match next_packet_details.outgoing_connector {
49764976
HopConnector::ShortChannelId(scid) => scid,
4977+
HopConnector::Dummy => {
4978+
// Dummy hops are only used for path padding and must not reach HTLC processing.
4979+
debug_assert!(false, "Dummy hop reached HTLC handling.");
4980+
return Err(LocalHTLCFailureReason::InvalidOnionPayload);
4981+
}
49774982
HopConnector::Trampoline(_) => {
49784983
return Err(LocalHTLCFailureReason::InvalidTrampolineForward);
49794984
}
@@ -6878,6 +6883,7 @@ where
68786883
fn process_pending_update_add_htlcs(&self) -> bool {
68796884
let mut should_persist = false;
68806885
let mut decode_update_add_htlcs = new_hash_map();
6886+
let mut dummy_update_add_htlcs = new_hash_map();
68816887
mem::swap(&mut decode_update_add_htlcs, &mut self.decode_update_add_htlcs.lock().unwrap());
68826888

68836889
let get_htlc_failure_type = |outgoing_scid_opt: Option<u64>, payment_hash: PaymentHash| {
@@ -6941,7 +6947,36 @@ where
69416947
&*self.logger,
69426948
&self.secp_ctx,
69436949
) {
6944-
Ok(decoded_onion) => decoded_onion,
6950+
Ok(decoded_onion) => match decoded_onion {
6951+
(
6952+
onion_utils::Hop::Dummy {
6953+
dummy_hop_data,
6954+
next_hop_hmac,
6955+
new_packet_bytes,
6956+
..
6957+
},
6958+
Some(next_packet_details),
6959+
) => {
6960+
let new_update_add_htlc =
6961+
onion_utils::peel_dummy_hop_update_add_htlc(
6962+
update_add_htlc,
6963+
dummy_hop_data,
6964+
next_hop_hmac,
6965+
new_packet_bytes,
6966+
next_packet_details,
6967+
&*self.node_signer,
6968+
&self.secp_ctx,
6969+
);
6970+
6971+
dummy_update_add_htlcs
6972+
.entry(incoming_scid_alias)
6973+
.or_insert_with(Vec::new)
6974+
.push(new_update_add_htlc);
6975+
6976+
continue;
6977+
},
6978+
_ => decoded_onion,
6979+
},
69456980

69466981
Err((htlc_fail, reason)) => {
69476982
let failure_type = HTLCHandlingFailureType::InvalidOnion;
@@ -6954,6 +6989,13 @@ where
69546989
let outgoing_scid_opt =
69556990
next_packet_details_opt.as_ref().and_then(|d| match d.outgoing_connector {
69566991
HopConnector::ShortChannelId(scid) => Some(scid),
6992+
HopConnector::Dummy => {
6993+
debug_assert!(
6994+
false,
6995+
"Dummy hops must never be processed at this stage."
6996+
);
6997+
None
6998+
},
69576999
HopConnector::Trampoline(_) => None,
69587000
});
69597001
let shared_secret = next_hop.shared_secret().secret_bytes();
@@ -7097,6 +7139,19 @@ where
70977139
));
70987140
}
70997141
}
7142+
7143+
// Merge peeled dummy HTLCs into the existing decode queue so they can be
7144+
// processed in the next iteration. We avoid replacing the whole queue
7145+
// (e.g. via mem::swap) because other threads may have enqueued new HTLCs
7146+
// meanwhile; merging preserves everything safely.
7147+
if !dummy_update_add_htlcs.is_empty() {
7148+
let mut decode_update_add_htlc_source = self.decode_update_add_htlcs.lock().unwrap();
7149+
7150+
for (incoming_scid_alias, htlcs) in dummy_update_add_htlcs.into_iter() {
7151+
decode_update_add_htlc_source.entry(incoming_scid_alias).or_default().extend(htlcs);
7152+
}
7153+
}
7154+
71007155
should_persist
71017156
}
71027157

lightning/src/ln/onion_payment.rs

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,7 @@ where
494494
L::Target: Logger,
495495
{
496496
let (hop, next_packet_details_opt) =
497-
decode_incoming_update_add_htlc_onion(msg, node_signer, logger, secp_ctx
497+
decode_incoming_update_add_htlc_onion(msg, &*node_signer, &*logger, secp_ctx
498498
).map_err(|(msg, failure_reason)| {
499499
let (reason, err_data) = match msg {
500500
HTLCFailureMsg::Malformed(_) => (failure_reason, Vec::new()),
@@ -532,6 +532,29 @@ where
532532
// onion here and check it.
533533
create_fwd_pending_htlc_info(msg, hop, shared_secret.secret_bytes(), Some(next_packet_pubkey))?
534534
},
535+
onion_utils::Hop::Dummy { dummy_hop_data, next_hop_hmac, new_packet_bytes, .. } => {
536+
let next_packet_details = match next_packet_details_opt {
537+
Some(next_packet_details) => next_packet_details,
538+
// Dummy Hops should always include the next hop details
539+
None => return Err(InboundHTLCErr {
540+
msg: "Failed to decode update add htlc onion",
541+
reason: LocalHTLCFailureReason::InvalidOnionPayload,
542+
err_data: Vec::new(),
543+
}),
544+
};
545+
546+
let new_update_add_htlc = onion_utils::peel_dummy_hop_update_add_htlc(
547+
msg,
548+
dummy_hop_data,
549+
next_hop_hmac,
550+
new_packet_bytes,
551+
next_packet_details,
552+
&*node_signer,
553+
secp_ctx
554+
);
555+
556+
peel_payment_onion(&new_update_add_htlc, node_signer, logger, secp_ctx, cur_height, allow_skimmed_fees)?
557+
},
535558
_ => {
536559
let shared_secret = hop.shared_secret().secret_bytes();
537560
create_recv_pending_htlc_info(
@@ -545,6 +568,8 @@ where
545568
pub(super) enum HopConnector {
546569
// scid-based routing
547570
ShortChannelId(u64),
571+
// Dummy hop for path padding
572+
Dummy,
548573
// Trampoline-based routing
549574
#[allow(unused)]
550575
Trampoline(PublicKey),
@@ -649,6 +674,22 @@ where
649674
outgoing_cltv_value
650675
})
651676
}
677+
onion_utils::Hop::Dummy { dummy_hop_data: msgs::InboundOnionDummyPayload { ref payment_relay, ref payment_constraints, .. }, shared_secret, .. } => {
678+
let (amt_to_forward, outgoing_cltv_value) = match check_blinded_forward(
679+
msg.amount_msat, msg.cltv_expiry, &payment_relay, &payment_constraints, &BlindedHopFeatures::empty()
680+
) {
681+
Ok((amt, cltv)) => (amt, cltv),
682+
Err(()) => {
683+
return encode_relay_error("Underflow calculating outbound amount or cltv value for blinded forward",
684+
LocalHTLCFailureReason::InvalidOnionBlinding, shared_secret.secret_bytes(), None, &[0; 32]);
685+
}
686+
};
687+
688+
let next_packet_pubkey = onion_utils::next_hop_pubkey(secp_ctx,
689+
msg.onion_routing_packet.public_key.unwrap(), &shared_secret.secret_bytes());
690+
691+
Some(NextPacketDetails { next_packet_pubkey, outgoing_connector: HopConnector::Dummy, outgoing_amt_msat: amt_to_forward, outgoing_cltv_value })
692+
}
652693
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, .. } => {
653694
let next_trampoline_packet_pubkey = onion_utils::next_hop_pubkey(secp_ctx,
654695
incoming_trampoline_public_key, &trampoline_shared_secret.secret_bytes());

lightning/src/ln/onion_utils.rs

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ use crate::crypto::streams::ChaChaReader;
1414
use crate::events::HTLCHandlingFailureReason;
1515
use crate::ln::channel::TOTAL_BITCOIN_SUPPLY_SATOSHIS;
1616
use crate::ln::channelmanager::{HTLCSource, RecipientOnionFields};
17-
use crate::ln::msgs::{self, DecodeError};
17+
use crate::ln::msgs::{self, DecodeError, InboundOnionDummyPayload, OnionPacket, UpdateAddHTLC};
18+
use crate::ln::onion_payment::{HopConnector, NextPacketDetails};
1819
use crate::offers::invoice_request::InvoiceRequest;
1920
use crate::routing::gossip::NetworkUpdate;
2021
use crate::routing::router::{BlindedTail, Path, RouteHop, RouteParameters, TrampolineHop};
@@ -2356,6 +2357,12 @@ where
23562357
new_packet_bytes,
23572358
})
23582359
},
2360+
msgs::InboundOnionPayload::Dummy(dummy_hop_data) => Ok(Hop::Dummy {
2361+
dummy_hop_data,
2362+
shared_secret,
2363+
next_hop_hmac,
2364+
new_packet_bytes,
2365+
}),
23592366
_ => {
23602367
if blinding_point.is_some() {
23612368
return Err(OnionDecodeErr::Malformed {
@@ -2533,6 +2540,61 @@ where
25332540
}
25342541
}
25352542

2543+
/// Peels a single dummy hop from an inbound `UpdateAddHTLC` by reconstructing the next
2544+
/// onion packet and HTLC state.
2545+
///
2546+
/// This helper is used when processing dummy hops in a blinded path. Dummy hops are not
2547+
/// forwarded on the network; instead, their onion layer is removed locally and a new
2548+
/// `UpdateAddHTLC` is constructed with the next onion packet and updated amount/CLTV
2549+
/// values.
2550+
///
2551+
/// This function performs no validation and does not enqueue or forward the HTLC.
2552+
/// It only reconstructs the next `UpdateAddHTLC` for further local processing.
2553+
pub(super) fn peel_dummy_hop_update_add_htlc<NS: Deref, T: secp256k1::Verification>(
2554+
msg: &UpdateAddHTLC, dummy_hop_data: InboundOnionDummyPayload, next_hop_hmac: [u8; 32],
2555+
new_packet_bytes: [u8; ONION_DATA_LEN], next_packet_details: NextPacketDetails,
2556+
node_signer: NS, secp_ctx: &Secp256k1<T>,
2557+
) -> UpdateAddHTLC
2558+
where
2559+
NS::Target: NodeSigner,
2560+
{
2561+
let NextPacketDetails {
2562+
next_packet_pubkey,
2563+
outgoing_amt_msat,
2564+
outgoing_connector,
2565+
outgoing_cltv_value,
2566+
} = next_packet_details;
2567+
2568+
debug_assert!(
2569+
matches!(outgoing_connector, HopConnector::Dummy),
2570+
"Dummy hop must always map to HopConnector::Dummy"
2571+
);
2572+
2573+
let next_blinding_point = dummy_hop_data
2574+
.intro_node_blinding_point
2575+
.or(msg.blinding_point)
2576+
.and_then(|blinding_point| {
2577+
let ss = node_signer.ecdh(Recipient::Node, &blinding_point, None).ok()?.secret_bytes();
2578+
2579+
next_hop_pubkey(secp_ctx, blinding_point, &ss).ok()
2580+
});
2581+
2582+
let new_onion_packet = OnionPacket {
2583+
version: 0,
2584+
public_key: next_packet_pubkey,
2585+
hop_data: new_packet_bytes,
2586+
hmac: next_hop_hmac,
2587+
};
2588+
2589+
UpdateAddHTLC {
2590+
onion_routing_packet: new_onion_packet,
2591+
blinding_point: next_blinding_point,
2592+
amount_msat: outgoing_amt_msat,
2593+
cltv_expiry: outgoing_cltv_value,
2594+
..msg.clone()
2595+
}
2596+
}
2597+
25362598
/// Build a payment onion, returning the first hop msat and cltv values as well.
25372599
/// `cur_block_height` should be set to the best known block height + 1.
25382600
pub fn create_payment_onion<T: secp256k1::Signing>(

0 commit comments

Comments
 (0)