Skip to content

Commit 08e50ff

Browse files
committed
Introduce DummyTlvs
Adds new `Dummy` variant to `ControlTlvs`, allowing insertion of arbitrary dummy hops before the final `ReceiveTlvs`. This increases the length of the blinded path, making it harder for a malicious actor to infer the position of the true final hop.
1 parent 15e264c commit 08e50ff

File tree

3 files changed

+92
-4
lines changed

3 files changed

+92
-4
lines changed

lightning/src/blinded_path/message.rs

+42-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
1212
use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey};
1313

14+
use crate::offers::signer;
1415
#[allow(unused_imports)]
1516
use crate::prelude::*;
1617

@@ -19,9 +20,9 @@ use crate::blinded_path::{BlindedHop, BlindedPath, Direction, IntroductionNode,
1920
use crate::crypto::streams::ChaChaPolyReadAdapter;
2021
use crate::io;
2122
use crate::io::Cursor;
22-
use crate::ln::channelmanager::PaymentId;
23+
use crate::ln::channelmanager::{PaymentId, Verification};
2324
use crate::ln::msgs::DecodeError;
24-
use crate::ln::onion_utils;
25+
use crate::ln::{inbound_payment, onion_utils};
2526
use crate::offers::nonce::Nonce;
2627
use crate::onion_message::packet::ControlTlvs;
2728
use crate::routing::gossip::{NodeId, ReadOnlyNetworkGraph};
@@ -258,6 +259,45 @@ pub(crate) struct ForwardTlvs {
258259
pub(crate) next_blinding_override: Option<PublicKey>,
259260
}
260261

262+
pub(crate) struct UnauthenticatedDummyTlvs {}
263+
264+
impl Writeable for UnauthenticatedDummyTlvs {
265+
fn write<W: Writer>(&self, _writer: &mut W) -> Result<(), io::Error> {
266+
Ok(())
267+
}
268+
}
269+
270+
impl Verification for UnauthenticatedDummyTlvs {
271+
/// Constructs an HMAC to include in [`OffersContext`] for the data along with the given
272+
/// [`Nonce`].
273+
fn hmac_data(&self, nonce: Nonce, expanded_key: &inbound_payment::ExpandedKey) -> Hmac<Sha256> {
274+
signer::hmac_for_dummy_tlvs(self, nonce, expanded_key)
275+
}
276+
277+
/// Authenticates the data using an HMAC and a [`Nonce`] taken from an [`OffersContext`].
278+
fn verify_data(
279+
&self, hmac: Hmac<Sha256>, nonce: Nonce, expanded_key: &inbound_payment::ExpandedKey,
280+
) -> Result<(), ()> {
281+
signer::verify_dummy_tlvs(self, hmac, nonce, expanded_key)
282+
}
283+
}
284+
285+
pub(crate) struct DummyTlvs {
286+
pub(crate) dummy_tlvs: UnauthenticatedDummyTlvs,
287+
/// An HMAC of `tlvs` along with a nonce used to construct it.
288+
pub(crate) authentication: (Hmac<Sha256>, Nonce),
289+
}
290+
291+
impl Writeable for DummyTlvs {
292+
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
293+
encode_tlv_stream!(writer, {
294+
(65539, self.authentication, required),
295+
});
296+
297+
Ok(())
298+
}
299+
}
300+
261301
/// Similar to [`ForwardTlvs`], but these TLVs are for the final node.
262302
pub(crate) struct ReceiveTlvs {
263303
/// If `context` is `Some`, it is used to identify the blinded path that this onion message is

lightning/src/offers/signer.rs

+24
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
//! Utilities for signing offer messages and verifying metadata.
1111
12+
use crate::blinded_path::message::UnauthenticatedDummyTlvs;
1213
use crate::blinded_path::payment::UnauthenticatedReceiveTlvs;
1314
use crate::ln::channelmanager::PaymentId;
1415
use crate::ln::inbound_payment::{ExpandedKey, IV_LEN};
@@ -555,3 +556,26 @@ pub(crate) fn verify_held_htlc_available_context(
555556
Err(())
556557
}
557558
}
559+
560+
pub(crate) fn hmac_for_dummy_tlvs(
561+
tlvs: &UnauthenticatedDummyTlvs, nonce: Nonce, expanded_key: &ExpandedKey,
562+
) -> Hmac<Sha256> {
563+
const IV_BYTES: &[u8; IV_LEN] = b"LDK Msgs Dummies";
564+
let mut hmac = expanded_key.hmac_for_offer();
565+
hmac.input(IV_BYTES);
566+
hmac.input(&nonce.0);
567+
hmac.input(PAYMENT_TLVS_HMAC_INPUT);
568+
tlvs.write(&mut hmac).unwrap();
569+
570+
Hmac::from_engine(hmac)
571+
}
572+
573+
pub(crate) fn verify_dummy_tlvs(
574+
tlvs: &UnauthenticatedDummyTlvs, hmac: Hmac<Sha256>, nonce: Nonce, expanded_key: &ExpandedKey,
575+
) -> Result<(), ()> {
576+
if hmac_for_dummy_tlvs(tlvs, nonce, expanded_key) == hmac {
577+
Ok(())
578+
} else {
579+
Err(())
580+
}
581+
}

lightning/src/onion_message/packet.rs

+26-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ use super::async_payments::AsyncPaymentsMessage;
1717
use super::dns_resolution::DNSResolverMessage;
1818
use super::messenger::CustomOnionMessageHandler;
1919
use super::offers::OffersMessage;
20-
use crate::blinded_path::message::{BlindedMessagePath, ForwardTlvs, NextMessageHop, ReceiveTlvs};
20+
use crate::blinded_path::message::{
21+
BlindedMessagePath, DummyTlvs, ForwardTlvs, NextMessageHop, ReceiveTlvs,
22+
UnauthenticatedDummyTlvs,
23+
};
2124
use crate::crypto::streams::{ChaChaPolyReadAdapter, ChaChaPolyWriteAdapter};
2225
use crate::ln::msgs::DecodeError;
2326
use crate::ln::onion_utils;
@@ -111,6 +114,8 @@ impl LengthReadable for Packet {
111114
pub(super) enum Payload<T: OnionMessageContents> {
112115
/// This payload is for an intermediate hop.
113116
Forward(ForwardControlTlvs),
117+
/// This payload is dummy, and is inteded to be peeled.
118+
Dummy(DummyControlTlvs),
114119
/// This payload is for the final hop.
115120
Receive { control_tlvs: ReceiveControlTlvs, reply_path: Option<BlindedMessagePath>, message: T },
116121
}
@@ -204,6 +209,11 @@ pub(super) enum ForwardControlTlvs {
204209
Unblinded(ForwardTlvs),
205210
}
206211

212+
pub(super) enum DummyControlTlvs {
213+
/// See [`ForwardControlTlvs::Unblinded`]
214+
Unblinded(DummyTlvs),
215+
}
216+
207217
/// Receive control TLVs in their blinded and unblinded form.
208218
pub(super) enum ReceiveControlTlvs {
209219
/// See [`ForwardControlTlvs::Blinded`].
@@ -234,6 +244,10 @@ impl<T: OnionMessageContents> Writeable for (Payload<T>, [u8; 32]) {
234244
let write_adapter = ChaChaPolyWriteAdapter::new(self.1, &control_tlvs);
235245
_encode_varint_length_prefixed_tlv!(w, { (4, write_adapter, required) })
236246
},
247+
Payload::Dummy(DummyControlTlvs::Unblinded(control_tlvs)) => {
248+
let write_adapter = ChaChaPolyWriteAdapter::new(self.1, &control_tlvs);
249+
_encode_varint_length_prefixed_tlv!(w, { (4, write_adapter, required) })
250+
},
237251
Payload::Receive {
238252
control_tlvs: ReceiveControlTlvs::Unblinded(control_tlvs),
239253
reply_path,
@@ -310,6 +324,9 @@ impl<H: CustomOnionMessageHandler + ?Sized, L: Logger + ?Sized> ReadableArgs<(Sh
310324
}
311325
Ok(Payload::Forward(ForwardControlTlvs::Unblinded(tlvs)))
312326
},
327+
Some(ChaChaPolyReadAdapter { readable: ControlTlvs::Dummy(tlvs) }) => {
328+
Ok(Payload::Dummy(DummyControlTlvs::Unblinded(tlvs)))
329+
},
313330
Some(ChaChaPolyReadAdapter { readable: ControlTlvs::Receive(tlvs) }) => {
314331
Ok(Payload::Receive {
315332
control_tlvs: ReceiveControlTlvs::Unblinded(tlvs),
@@ -328,6 +345,8 @@ impl<H: CustomOnionMessageHandler + ?Sized, L: Logger + ?Sized> ReadableArgs<(Sh
328345
pub(crate) enum ControlTlvs {
329346
/// This onion message is intended to be forwarded.
330347
Forward(ForwardTlvs),
348+
/// This onion message is a dummy, and is intended to be peeled.
349+
Dummy(DummyTlvs),
331350
/// This onion message is intended to be received.
332351
Receive(ReceiveTlvs),
333352
}
@@ -343,6 +362,7 @@ impl Readable for ControlTlvs {
343362
(4, next_node_id, option),
344363
(8, next_blinding_override, option),
345364
(65537, context, option),
365+
(65539, authentication, option),
346366
});
347367

348368
let next_hop = match (short_channel_id, next_node_id) {
@@ -363,7 +383,10 @@ impl Readable for ControlTlvs {
363383
} else if valid_recv_fmt {
364384
ControlTlvs::Receive(ReceiveTlvs { context })
365385
} else {
366-
return Err(DecodeError::InvalidValue);
386+
ControlTlvs::Dummy(DummyTlvs {
387+
dummy_tlvs: UnauthenticatedDummyTlvs {},
388+
authentication: authentication.ok_or(DecodeError::InvalidValue)?,
389+
})
367390
};
368391

369392
Ok(payload_fmt)
@@ -374,6 +397,7 @@ impl Writeable for ControlTlvs {
374397
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
375398
match self {
376399
Self::Forward(tlvs) => tlvs.write(w),
400+
Self::Dummy(tlvs) => tlvs.write(w),
377401
Self::Receive(tlvs) => tlvs.write(w),
378402
}
379403
}

0 commit comments

Comments
 (0)