Skip to content

Commit 65206f0

Browse files
committed
Always pad BlindedMessagePath hop data to a consistent length
If we're building a blinded message path with extra dummy hops, we have to ensure we at least hide the length of the data in pre-final hops as otherwise the dummy hops are trivially obvious. Here we do so, taking an extra `bool` parameter to `BlindedMessagePath` constructors to decide whether to pad every hop to the existing `MESSAGE_PADDING_ROUND_OFF` or whether to only ensure that each non-final hop has an identical hop data length. In cases where the `DefaultMessageRouter` opts to use compact paths, it now also selects compact padding, whether short channel IDs are available or not.
1 parent b91dacb commit 65206f0

File tree

6 files changed

+202
-141
lines changed

6 files changed

+202
-141
lines changed

lightning-dns-resolver/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ mod test {
236236
recipient,
237237
local_node_receive_key,
238238
context,
239+
false,
239240
&keys,
240241
secp_ctx,
241242
)])
@@ -345,6 +346,7 @@ mod test {
345346
payer_id,
346347
receive_key,
347348
query_context,
349+
false,
348350
&*payer_keys,
349351
&secp_ctx,
350352
);

lightning/src/blinded_path/message.rs

Lines changed: 70 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -54,21 +54,37 @@ impl Readable for BlindedMessagePath {
5454

5555
impl BlindedMessagePath {
5656
/// Create a one-hop blinded path for a message.
57+
///
58+
/// `compact_padding` selects between space-inefficient padding that better hides contents and
59+
/// a space-constrained padding that does very little to hide the contents, especially for the
60+
/// last hop. It should only be set when the blinded path needs to be as compact as possible.
5761
pub fn one_hop<ES: Deref, T: secp256k1::Signing + secp256k1::Verification>(
5862
recipient_node_id: PublicKey, local_node_receive_key: ReceiveAuthKey,
59-
context: MessageContext, entropy_source: ES, secp_ctx: &Secp256k1<T>,
63+
context: MessageContext, compact_padding: bool, entropy_source: ES, secp_ctx: &Secp256k1<T>,
6064
) -> Self
6165
where
6266
ES::Target: EntropySource,
6367
{
64-
Self::new(&[], recipient_node_id, local_node_receive_key, context, entropy_source, secp_ctx)
68+
Self::new(
69+
&[],
70+
recipient_node_id,
71+
local_node_receive_key,
72+
context,
73+
compact_padding,
74+
entropy_source,
75+
secp_ctx,
76+
)
6577
}
6678

6779
/// Create a path for an onion message, to be forwarded along `node_pks`.
80+
///
81+
/// `compact_padding` selects between space-inefficient padding that better hides contents and
82+
/// a space-constrained padding that does very little to hide the contents, especially for the
83+
/// last hop. It should only be set when the blinded path needs to be as compact as possible.
6884
pub fn new<ES: Deref, T: secp256k1::Signing + secp256k1::Verification>(
6985
intermediate_nodes: &[MessageForwardNode], recipient_node_id: PublicKey,
70-
local_node_receive_key: ReceiveAuthKey, context: MessageContext, entropy_source: ES,
71-
secp_ctx: &Secp256k1<T>,
86+
local_node_receive_key: ReceiveAuthKey, context: MessageContext, compact_padding: bool,
87+
entropy_source: ES, secp_ctx: &Secp256k1<T>,
7288
) -> Self
7389
where
7490
ES::Target: EntropySource,
@@ -79,19 +95,24 @@ impl BlindedMessagePath {
7995
0,
8096
local_node_receive_key,
8197
context,
98+
compact_padding,
8299
entropy_source,
83100
secp_ctx,
84101
)
85102
}
86103

87104
/// Same as [`BlindedMessagePath::new`], but allows specifying a number of dummy hops.
88105
///
89-
/// Note:
90-
/// At most [`MAX_DUMMY_HOPS_COUNT`] dummy hops can be added to the blinded path.
106+
///
107+
/// `compact_padding` selects between space-inefficient padding that better hides contents and
108+
/// a space-constrained padding that does very little to hide the contents, especially for the
109+
/// last hop. It should only be set when the blinded path needs to be as compact as possible.
110+
///
111+
/// Note: At most [`MAX_DUMMY_HOPS_COUNT`] dummy hops can be added to the blinded path.
91112
pub fn new_with_dummy_hops<ES: Deref, T: secp256k1::Signing + secp256k1::Verification>(
92113
intermediate_nodes: &[MessageForwardNode], recipient_node_id: PublicKey,
93114
dummy_hop_count: usize, local_node_receive_key: ReceiveAuthKey, context: MessageContext,
94-
entropy_source: ES, secp_ctx: &Secp256k1<T>,
115+
compact_padding: bool, entropy_source: ES, secp_ctx: &Secp256k1<T>,
95116
) -> Self
96117
where
97118
ES::Target: EntropySource,
@@ -114,6 +135,7 @@ impl BlindedMessagePath {
114135
context,
115136
&blinding_secret,
116137
local_node_receive_key,
138+
compact_padding,
117139
),
118140
})
119141
}
@@ -714,7 +736,7 @@ pub const MAX_DUMMY_HOPS_COUNT: usize = 10;
714736
pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
715737
secp_ctx: &Secp256k1<T>, intermediate_nodes: &[MessageForwardNode],
716738
recipient_node_id: PublicKey, dummy_hop_count: usize, context: MessageContext,
717-
session_priv: &SecretKey, local_node_receive_key: ReceiveAuthKey,
739+
session_priv: &SecretKey, local_node_receive_key: ReceiveAuthKey, compact_padding: bool,
718740
) -> Vec<BlindedHop> {
719741
let dummy_count = cmp::min(dummy_hop_count, MAX_DUMMY_HOPS_COUNT);
720742
let pks = intermediate_nodes
@@ -724,9 +746,8 @@ pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
724746
core::iter::repeat((recipient_node_id, Some(local_node_receive_key))).take(dummy_count),
725747
)
726748
.chain(core::iter::once((recipient_node_id, Some(local_node_receive_key))));
727-
let is_compact = intermediate_nodes.iter().any(|node| node.short_channel_id.is_some());
728749

729-
let tlvs = pks
750+
let intermediate_tlvs = pks
730751
.clone()
731752
.skip(1) // The first node's TLVs contains the next node's pubkey
732753
.zip(intermediate_nodes.iter().map(|node| node.short_channel_id))
@@ -737,18 +758,44 @@ pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
737758
.map(|next_hop| {
738759
ControlTlvs::Forward(ForwardTlvs { next_hop, next_blinding_override: None })
739760
})
740-
.chain((0..dummy_count).map(|_| ControlTlvs::Dummy))
741-
.chain(core::iter::once(ControlTlvs::Receive(ReceiveTlvs { context: Some(context) })));
742-
743-
if is_compact {
744-
let path = pks.zip(tlvs);
745-
utils::construct_blinded_hops(secp_ctx, path, session_priv)
761+
.chain((0..dummy_count).map(|_| ControlTlvs::Dummy));
762+
763+
let max_intermediate_len =
764+
intermediate_tlvs.clone().map(|tlvs| tlvs.serialized_length()).max().unwrap_or(0);
765+
let have_intermediate_one_byte_smaller =
766+
intermediate_tlvs.clone().any(|tlvs| tlvs.serialized_length() == max_intermediate_len - 1);
767+
768+
let round_off = if compact_padding {
769+
// We can only pad by a minimum of two bytes. Thus, if there are any intermediate hops that
770+
// need to be padded by exactly one byte, we have to instead pad everything by two.
771+
if have_intermediate_one_byte_smaller {
772+
max_intermediate_len + 2
773+
} else {
774+
max_intermediate_len
775+
}
746776
} else {
747-
let path =
748-
pks.zip(tlvs.map(|tlv| BlindedPathWithPadding {
749-
tlvs: tlv,
750-
round_off: MESSAGE_PADDING_ROUND_OFF,
751-
}));
752-
utils::construct_blinded_hops(secp_ctx, path, session_priv)
753-
}
777+
MESSAGE_PADDING_ROUND_OFF
778+
};
779+
780+
let tlvs = intermediate_tlvs.map(|tlvs| {
781+
let res = BlindedPathWithPadding {
782+
tlvs,
783+
round_off,
784+
};
785+
if compact_padding {
786+
debug_assert_eq!(res.serialized_length(), max_intermediate_len);
787+
} else {
788+
// We don't currently ever push extra stuff to intermediate hops, so simply assert that
789+
// the fully-padded hops are always `MESSAGE_PADDING_ROUND_OFF` long.
790+
debug_assert_eq!(res.serialized_length(), MESSAGE_PADDING_ROUND_OFF);
791+
}
792+
res
793+
})
794+
.chain(core::iter::once(BlindedPathWithPadding {
795+
tlvs: ControlTlvs::Receive(ReceiveTlvs { context: Some(context) }),
796+
round_off: if compact_padding { 0 } else { MESSAGE_PADDING_ROUND_OFF },
797+
}));
798+
799+
let path = pks.zip(tlvs);
800+
utils::construct_blinded_hops(secp_ctx, path, session_priv)
754801
}

lightning/src/blinded_path/utils.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -256,9 +256,12 @@ impl<T: Writeable> Writeable for BlindedPathWithPadding<T> {
256256
let tlv_length = self.tlvs.serialized_length();
257257
let total_length = tlv_length + TLV_OVERHEAD;
258258

259-
let padding_length = total_length.div_ceil(self.round_off) * self.round_off - total_length;
260-
261-
let padding = Some(BlindedPathPadding::new(padding_length));
259+
let padding = if self.round_off == 0 || tlv_length % self.round_off == 0 {
260+
None
261+
} else {
262+
let length = total_length.div_ceil(self.round_off) * self.round_off - total_length;
263+
Some(BlindedPathPadding::new(length))
264+
};
262265

263266
encode_tlv_stream!(writer, {
264267
(1, padding, option),

lightning/src/offers/flow.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1319,6 +1319,7 @@ where
13191319
num_dummy_hops,
13201320
self.receive_auth_key,
13211321
context,
1322+
false,
13221323
&*entropy,
13231324
&self.secp_ctx,
13241325
)

0 commit comments

Comments
 (0)