Skip to content

Commit c7d1621

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 94c7f9e commit c7d1621

File tree

6 files changed

+202
-142
lines changed

6 files changed

+202
-142
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: 69 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -54,21 +54,38 @@ 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,
64+
secp_ctx: &Secp256k1<T>,
6065
) -> Self
6166
where
6267
ES::Target: EntropySource,
6368
{
64-
Self::new(&[], recipient_node_id, local_node_receive_key, context, entropy_source, secp_ctx)
69+
Self::new(
70+
&[],
71+
recipient_node_id,
72+
local_node_receive_key,
73+
context,
74+
compact_padding,
75+
entropy_source,
76+
secp_ctx,
77+
)
6578
}
6679

6780
/// Create a path for an onion message, to be forwarded along `node_pks`.
81+
///
82+
/// `compact_padding` selects between space-inefficient padding that better hides contents and
83+
/// a space-constrained padding that does very little to hide the contents, especially for the
84+
/// last hop. It should only be set when the blinded path needs to be as compact as possible.
6885
pub fn new<ES: Deref, T: secp256k1::Signing + secp256k1::Verification>(
6986
intermediate_nodes: &[MessageForwardNode], recipient_node_id: PublicKey,
70-
local_node_receive_key: ReceiveAuthKey, context: MessageContext, entropy_source: ES,
71-
secp_ctx: &Secp256k1<T>,
87+
local_node_receive_key: ReceiveAuthKey, context: MessageContext, compact_padding: bool,
88+
entropy_source: ES, secp_ctx: &Secp256k1<T>,
7289
) -> Self
7390
where
7491
ES::Target: EntropySource,
@@ -79,19 +96,24 @@ impl BlindedMessagePath {
7996
0,
8097
local_node_receive_key,
8198
context,
99+
compact_padding,
82100
entropy_source,
83101
secp_ctx,
84102
)
85103
}
86104

87105
/// Same as [`BlindedMessagePath::new`], but allows specifying a number of dummy hops.
88106
///
89-
/// Note:
90-
/// At most [`MAX_DUMMY_HOPS_COUNT`] dummy hops can be added to the blinded path.
107+
///
108+
/// `compact_padding` selects between space-inefficient padding that better hides contents and
109+
/// a space-constrained padding that does very little to hide the contents, especially for the
110+
/// last hop. It should only be set when the blinded path needs to be as compact as possible.
111+
///
112+
/// Note: At most [`MAX_DUMMY_HOPS_COUNT`] dummy hops can be added to the blinded path.
91113
pub fn new_with_dummy_hops<ES: Deref, T: secp256k1::Signing + secp256k1::Verification>(
92114
intermediate_nodes: &[MessageForwardNode], recipient_node_id: PublicKey,
93115
dummy_hop_count: usize, local_node_receive_key: ReceiveAuthKey, context: MessageContext,
94-
entropy_source: ES, secp_ctx: &Secp256k1<T>,
116+
compact_padding: bool, entropy_source: ES, secp_ctx: &Secp256k1<T>,
95117
) -> Self
96118
where
97119
ES::Target: EntropySource,
@@ -114,6 +136,7 @@ impl BlindedMessagePath {
114136
context,
115137
&blinding_secret,
116138
local_node_receive_key,
139+
compact_padding,
117140
),
118141
})
119142
}
@@ -714,7 +737,7 @@ pub const MAX_DUMMY_HOPS_COUNT: usize = 10;
714737
pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
715738
secp_ctx: &Secp256k1<T>, intermediate_nodes: &[MessageForwardNode],
716739
recipient_node_id: PublicKey, dummy_hop_count: usize, context: MessageContext,
717-
session_priv: &SecretKey, local_node_receive_key: ReceiveAuthKey,
740+
session_priv: &SecretKey, local_node_receive_key: ReceiveAuthKey, compact_padding: bool,
718741
) -> Vec<BlindedHop> {
719742
let dummy_count = cmp::min(dummy_hop_count, MAX_DUMMY_HOPS_COUNT);
720743
let pks = intermediate_nodes
@@ -724,9 +747,8 @@ pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
724747
core::iter::repeat((recipient_node_id, Some(local_node_receive_key))).take(dummy_count),
725748
)
726749
.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());
728750

729-
let tlvs = pks
751+
let intermediate_tlvs = pks
730752
.clone()
731753
.skip(1) // The first node's TLVs contains the next node's pubkey
732754
.zip(intermediate_nodes.iter().map(|node| node.short_channel_id))
@@ -737,18 +759,42 @@ pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
737759
.map(|next_hop| {
738760
ControlTlvs::Forward(ForwardTlvs { next_hop, next_blinding_override: None })
739761
})
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)
762+
.chain((0..dummy_count).map(|_| ControlTlvs::Dummy));
763+
764+
let max_intermediate_len =
765+
intermediate_tlvs.clone().map(|tlvs| tlvs.serialized_length()).max().unwrap_or(0);
766+
let have_intermediate_one_byte_smaller =
767+
intermediate_tlvs.clone().any(|tlvs| tlvs.serialized_length() == max_intermediate_len - 1);
768+
769+
let round_off = if compact_padding {
770+
// We can only pad by a minimum of two bytes. Thus, if there are any intermediate hops that
771+
// need to be padded by exactly one byte, we have to instead pad everything by two.
772+
if have_intermediate_one_byte_smaller {
773+
max_intermediate_len + 2
774+
} else {
775+
max_intermediate_len
776+
}
746777
} 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-
}
778+
MESSAGE_PADDING_ROUND_OFF
779+
};
780+
781+
let tlvs = intermediate_tlvs
782+
.map(|tlvs| {
783+
let res = BlindedPathWithPadding { tlvs, round_off };
784+
if compact_padding {
785+
debug_assert_eq!(res.serialized_length(), max_intermediate_len);
786+
} else {
787+
// We don't currently ever push extra stuff to intermediate hops, so simply assert that
788+
// the fully-padded hops are always `MESSAGE_PADDING_ROUND_OFF` long.
789+
debug_assert_eq!(res.serialized_length(), MESSAGE_PADDING_ROUND_OFF);
790+
}
791+
res
792+
})
793+
.chain(core::iter::once(BlindedPathWithPadding {
794+
tlvs: ControlTlvs::Receive(ReceiveTlvs { context: Some(context) }),
795+
round_off: if compact_padding { 0 } else { MESSAGE_PADDING_ROUND_OFF },
796+
}));
797+
798+
let path = pks.zip(tlvs);
799+
utils::construct_blinded_hops(secp_ctx, path, session_priv)
754800
}

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)