Skip to content

Commit 224ef96

Browse files
committed
Introduce DummyTlv for blinded path privacy
DummyTlv represents an empty TLV inserted immediately before the actual ReceiveTlvs in a blinded path. These dummy hops are recursively authenticated like real ones, but carry no content. By allowing arbitrary dummy hops before the final ReceiveTlvs, we can obscure the true position of the recipient in the route. This makes it harder for a malicious onlooker to infer the actual destination, thereby strengthening recipient privacy.
1 parent 4ac1fda commit 224ef96

File tree

2 files changed

+44
-13
lines changed

2 files changed

+44
-13
lines changed

lightning/src/blinded_path/message.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,23 @@ pub(crate) struct ForwardTlvs {
266266
pub(crate) next_blinding_override: Option<PublicKey>,
267267
}
268268

269+
/// Represents the dummy TLV encoded immediately before the actual [`ReceiveTlvs`] in a blinded path.
270+
/// These TLVs are intended for the final node and are recursively authenticated until the real
271+
/// [`ReceiveTlvs`] is reached.
272+
///
273+
/// Their purpose is to arbitrarily extend the path length, obscuring the receiver's position in the
274+
/// route and thereby enhancing privacy.
275+
pub(crate) struct DummyTlv;
276+
277+
impl Writeable for DummyTlv {
278+
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
279+
encode_tlv_stream!(writer, {
280+
(65539, (), required),
281+
});
282+
Ok(())
283+
}
284+
}
285+
269286
/// Similar to [`ForwardTlvs`], but these TLVs are for the final node.
270287
pub(crate) struct ReceiveTlvs {
271288
/// If `context` is `Some`, it is used to identify the blinded path that this onion message is

lightning/src/onion_message/packet.rs

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ use super::async_payments::AsyncPaymentsMessage;
1616
use super::dns_resolution::DNSResolverMessage;
1717
use super::messenger::CustomOnionMessageHandler;
1818
use super::offers::OffersMessage;
19-
use crate::blinded_path::message::{BlindedMessagePath, ForwardTlvs, NextMessageHop, ReceiveTlvs};
19+
use crate::blinded_path::message::{
20+
BlindedMessagePath, DummyTlv, ForwardTlvs, NextMessageHop, ReceiveTlvs,
21+
};
2022
use crate::crypto::streams::{ChaChaDualPolyReadAdapter, ChaChaPolyWriteAdapter};
2123
use crate::ln::msgs::DecodeError;
2224
use crate::ln::onion_utils;
@@ -111,6 +113,12 @@ impl LengthReadable for Packet {
111113
pub(super) enum Payload<T: OnionMessageContents> {
112114
/// This payload is for an intermediate hop.
113115
Forward(ForwardControlTlvs),
116+
/// This payload is a dummy hop, and is intended to be peeled.
117+
Dummy {
118+
/// The payload was authenticated with the additional key that was
119+
/// provided to [`ReadableArgs::read`].
120+
control_tlvs_authenticated: bool,
121+
},
114122
/// This payload is for the final hop.
115123
Receive {
116124
/// The [`ReceiveControlTlvs`] were authenticated with the additional key which was
@@ -237,6 +245,10 @@ impl<T: OnionMessageContents> Writeable for (Payload<T>, [u8; 32]) {
237245
let write_adapter = ChaChaPolyWriteAdapter::new(self.1, &control_tlvs);
238246
_encode_varint_length_prefixed_tlv!(w, { (4, write_adapter, required) })
239247
},
248+
Payload::Dummy { control_tlvs_authenticated: _ } => {
249+
let write_adapter = ChaChaPolyWriteAdapter::new(self.1, &DummyTlv);
250+
_encode_varint_length_prefixed_tlv!(w, { (4, write_adapter, required) })
251+
},
240252
Payload::Receive {
241253
control_tlvs: ReceiveControlTlvs::Unblinded(control_tlvs),
242254
reply_path,
@@ -316,6 +328,9 @@ impl<H: CustomOnionMessageHandler + ?Sized, L: Logger + ?Sized>
316328
}
317329
Ok(Payload::Forward(ForwardControlTlvs::Unblinded(tlvs)))
318330
},
331+
Some(ChaChaDualPolyReadAdapter { readable: ControlTlvs::Dummy, used_aad }) => {
332+
Ok(Payload::Dummy { control_tlvs_authenticated: used_aad })
333+
},
319334
Some(ChaChaDualPolyReadAdapter { readable: ControlTlvs::Receive(tlvs), used_aad }) => {
320335
Ok(Payload::Receive {
321336
control_tlvs: ReceiveControlTlvs::Unblinded(tlvs),
@@ -335,6 +350,8 @@ impl<H: CustomOnionMessageHandler + ?Sized, L: Logger + ?Sized>
335350
pub(crate) enum ControlTlvs {
336351
/// This onion message is intended to be forwarded.
337352
Forward(ForwardTlvs),
353+
/// This onion message is a dummy, and is intended to be peeled by the final recipient.
354+
Dummy,
338355
/// This onion message is intended to be received.
339356
Receive(ReceiveTlvs),
340357
}
@@ -350,6 +367,7 @@ impl Readable for ControlTlvs {
350367
(4, next_node_id, option),
351368
(8, next_blinding_override, option),
352369
(65537, context, option),
370+
(65539, is_dummy, option),
353371
});
354372

355373
let next_hop = match (short_channel_id, next_node_id) {
@@ -359,18 +377,13 @@ impl Readable for ControlTlvs {
359377
(None, None) => None,
360378
};
361379

362-
let valid_fwd_fmt = next_hop.is_some();
363-
let valid_recv_fmt = next_hop.is_none() && next_blinding_override.is_none();
364-
365-
let payload_fmt = if valid_fwd_fmt {
366-
ControlTlvs::Forward(ForwardTlvs {
367-
next_hop: next_hop.unwrap(),
368-
next_blinding_override,
369-
})
370-
} else if valid_recv_fmt {
371-
ControlTlvs::Receive(ReceiveTlvs { context })
372-
} else {
373-
return Err(DecodeError::InvalidValue);
380+
let payload_fmt = match (next_hop, next_blinding_override, is_dummy) {
381+
(Some(hop), _, None) => {
382+
ControlTlvs::Forward(ForwardTlvs { next_hop: hop, next_blinding_override })
383+
},
384+
(None, None, Some(())) => ControlTlvs::Dummy,
385+
(None, None, None) => ControlTlvs::Receive(ReceiveTlvs { context }),
386+
_ => return Err(DecodeError::InvalidValue),
374387
};
375388

376389
Ok(payload_fmt)
@@ -381,6 +394,7 @@ impl Writeable for ControlTlvs {
381394
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
382395
match self {
383396
Self::Forward(tlvs) => tlvs.write(w),
397+
Self::Dummy => DummyTlv.write(w),
384398
Self::Receive(tlvs) => tlvs.write(w),
385399
}
386400
}

0 commit comments

Comments
 (0)