Introduce parsing logic for DummyTlvs
What changed, and why it matters
This commit adds code to handle 'dummy' hops in onion-routed Lightning messages. Previously, the code had a placeholder TODO noting that dummy hops were not fully supported. The change lets the messenger recognize a Dummy payload, verify its authentication, build the next onion packet, and continue peeling the onion. It also refactors repeated packet-building code into a helper. There is no direct evidence in the commit or supplied references that this fixes an active security vulnerability; it appears to be a feature completion that removes an unhandled edge case.
Review as a normal feature/refactor commit. Verify that the control_tlvs_authenticated check is sufficient and that recursive peeling of dummy hops cannot be abused for denial-of-service (e.g., excessive recursion depth or resource consumption). No immediate security patch action is indicated by the commit alone.
Security signals we found
New handling for Payload::Dummy with authentication check before forwarding
Refactoring of packet/blinding-point construction into a shared closure
Removal of a TODO indicating dummy hops were previously unhandled
Recursive peel_onion_message call for dummy payloads
Evidence from the diff
The diff introduces a Payload::Dummy arm in the onion-message decoding flow inside lightning/src/onion_message/messenger.rs. It checks control_tlvs_authenticated, constructs the next OnionMessage via a new build_outbound_onion_message closure (which computes the next packet public key and next blinding point), and recursively calls peel_onion_message. The existing Forward arm is refactored to use the same closure, replacing inline logic and a TODO about dummy hops. No cryptographic constants, bounds checks, or memory-safety primitives are visibly altered beyond the new control flow.
Changed components
lightning/src/onion_message/messenger.rsonion message forwarding/decoding pathPayload::Dummy handlingInspect captured patch +61 / −40
diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs
index 553977d..bb8cbba 100644
--- a/lightning/src/onion_message/messenger.rs
+++ b/lightning/src/onion_message/messenger.rs
@@ -1144,6 +1144,44 @@ where
msg.onion_routing_packet.hmac,
(control_tlvs_ss, custom_handler.deref(), receiving_context_auth_key, logger.deref()),
);
+
+ // Constructs the next onion message using packet data and blinding logic.
+ let build_outbound_onion_message = |packet_pubkey: PublicKey,
+ next_hop_hmac: [u8; 32],
+ new_packet_bytes: Vec<u8>,
+ blinding_point_opt: Option<PublicKey>|
+ -> Result<OnionMessage, ()> {
+ let new_pubkey =
+ match onion_utils::next_hop_pubkey(&secp_ctx, packet_pubkey, &onion_decode_ss) {
+ Ok(pk) => pk,
+ Err(e) => {
+ log_trace!(logger, "Failed to compute next hop packet pubkey: {}", e);
+ return Err(());
+ },
+ };
+ let outgoing_packet = Packet {
+ version: 0,
+ public_key: new_pubkey,
+ hop_data: new_packet_bytes,
+ hmac: next_hop_hmac,
+ };
+ let blinding_point = match blinding_point_opt {
+ Some(bp) => bp,
+ None => match onion_utils::next_hop_pubkey(
+ &secp_ctx,
+ msg.blinding_point,
+ control_tlvs_ss.as_ref(),
+ ) {
+ Ok(bp) => bp,
+ Err(e) => {
+ log_trace!(logger, "Failed to compute next blinding point: {}", e);
+ return Err(());
+ },
+ },
+ };
+ Ok(OnionMessage { blinding_point, onion_routing_packet: outgoing_packet })
+ };
+
match next_hop {
Ok((
Payload::Receive {
@@ -1216,6 +1254,23 @@ where
Err(())
},
},
+ Ok((
+ Payload::Dummy { control_tlvs_authenticated },
+ Some((next_hop_hmac, new_packet_bytes)),
+ )) => {
+ if !control_tlvs_authenticated {
+ log_trace!(logger, "Received an unauthenticated dummy onion message");
+ return Err(());
+ }
+
+ let onion_message = build_outbound_onion_message(
+ msg.onion_routing_packet.public_key,
+ next_hop_hmac,
+ new_packet_bytes,
+ None,
+ )?;
+ peel_onion_message(&onion_message, secp_ctx, node_signer, logger, custom_handler)
+ },
Ok((
Payload::Forward(ForwardControlTlvs::Unblinded(ForwardTlvs {
next_hop,
@@ -1223,46 +1278,12 @@ where
})),
Some((next_hop_hmac, new_packet_bytes)),
)) => {
- // TODO: we need to check whether `next_hop` is our node, in which case this is a dummy
- // blinded hop and this onion message is destined for us. In this situation, we should keep
- // unwrapping the onion layers to get to the final payload. Since we don't have the option
- // of creating blinded paths with dummy hops currently, we should be ok to not handle this
- // for now.
- let packet_pubkey = msg.onion_routing_packet.public_key;
- let new_pubkey_opt =
- onion_utils::next_hop_pubkey(&secp_ctx, packet_pubkey, &onion_decode_ss);
- let new_pubkey = match new_pubkey_opt {
- Ok(pk) => pk,
- Err(e) => {
- log_trace!(logger, "Failed to compute next hop packet pubkey: {}", e);
- return Err(());
- },
- };
- let outgoing_packet = Packet {
- version: 0,
- public_key: new_pubkey,
- hop_data: new_packet_bytes,
- hmac: next_hop_hmac,
- };
- let onion_message = OnionMessage {
- blinding_point: match next_blinding_override {
- Some(blinding_point) => blinding_point,
- None => {
- match onion_utils::next_hop_pubkey(
- &secp_ctx,
- msg.blinding_point,
- control_tlvs_ss.as_ref(),
- ) {
- Ok(bp) => bp,
- Err(e) => {
- log_trace!(logger, "Failed to compute next blinding point: {}", e);
- return Err(());
- },
- }
- },
- },
- onion_routing_packet: outgoing_packet,
- };
+ let onion_message = build_outbound_onion_message(
+ msg.onion_routing_packet.public_key,
+ next_hop_hmac,
+ new_packet_bytes,
+ next_blinding_override,
+ )?;
Ok(PeeledOnion::Forward(next_hop, onion_message))
},
Why this scored 28/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.