Skip to content

Commit 72c69bb

Browse files
authored
Merge pull request #4213 from TheBlueMatt/2025-10-pad-use-non-compact-bps
Default to padding blinded paths
2 parents f036788 + d7b86e5 commit 72c69bb

File tree

10 files changed

+389
-229
lines changed

10 files changed

+389
-229
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: 99 additions & 32 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 which better hides contents and
59+
/// a space-constrained padding which 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 which better hides contents and
83+
/// a space-constrained padding which 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,23 @@ 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+
/// `compact_padding` selects between space-inefficient padding which better hides contents and
108+
/// a space-constrained padding which 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
}
@@ -416,28 +438,45 @@ pub enum OffersContext {
416438
/// Useful to timeout async recipients that are no longer supported as clients.
417439
path_absolute_expiry: Duration,
418440
},
419-
/// Context used by a [`BlindedMessagePath`] within a [`Refund`] or as a reply path for an
420-
/// [`InvoiceRequest`].
441+
/// Context used by a [`BlindedMessagePath`] within a [`Refund`].
421442
///
422443
/// This variant is intended to be received when handling a [`Bolt12Invoice`] or an
423444
/// [`InvoiceError`].
424445
///
425446
/// [`Refund`]: crate::offers::refund::Refund
426-
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
427447
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
428448
/// [`InvoiceError`]: crate::offers::invoice_error::InvoiceError
429-
OutboundPayment {
430-
/// Payment ID used when creating a [`Refund`] or [`InvoiceRequest`].
449+
OutboundPaymentForRefund {
450+
/// Payment ID used when creating a [`Refund`].
431451
///
432452
/// [`Refund`]: crate::offers::refund::Refund
433-
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
434453
payment_id: PaymentId,
435454

436-
/// A nonce used for authenticating that a [`Bolt12Invoice`] is for a valid [`Refund`] or
437-
/// [`InvoiceRequest`] and for deriving their signing keys.
455+
/// A nonce used for authenticating that a [`Bolt12Invoice`] is for a valid [`Refund`] and
456+
/// for deriving its signing keys.
438457
///
439458
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
440459
/// [`Refund`]: crate::offers::refund::Refund
460+
nonce: Nonce,
461+
},
462+
/// Context used by a [`BlindedMessagePath`] as a reply path for an [`InvoiceRequest`].
463+
///
464+
/// This variant is intended to be received when handling a [`Bolt12Invoice`] or an
465+
/// [`InvoiceError`].
466+
///
467+
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
468+
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
469+
/// [`InvoiceError`]: crate::offers::invoice_error::InvoiceError
470+
OutboundPaymentForOffer {
471+
/// Payment ID used when creating an [`InvoiceRequest`].
472+
///
473+
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
474+
payment_id: PaymentId,
475+
476+
/// A nonce used for authenticating that a [`Bolt12Invoice`] is for a valid
477+
/// [`InvoiceRequest`] and for deriving its signing keys.
478+
///
479+
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
441480
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
442481
nonce: Nonce,
443482
},
@@ -619,7 +658,7 @@ impl_writeable_tlv_based_enum!(OffersContext,
619658
(0, InvoiceRequest) => {
620659
(0, nonce, required),
621660
},
622-
(1, OutboundPayment) => {
661+
(1, OutboundPaymentForRefund) => {
623662
(0, payment_id, required),
624663
(1, nonce, required),
625664
},
@@ -631,6 +670,10 @@ impl_writeable_tlv_based_enum!(OffersContext,
631670
(2, invoice_slot, required),
632671
(4, path_absolute_expiry, required),
633672
},
673+
(4, OutboundPaymentForOffer) => {
674+
(0, payment_id, required),
675+
(1, nonce, required),
676+
},
634677
);
635678

636679
impl_writeable_tlv_based_enum!(AsyncPaymentsContext,
@@ -693,7 +736,7 @@ pub const MAX_DUMMY_HOPS_COUNT: usize = 10;
693736
pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
694737
secp_ctx: &Secp256k1<T>, intermediate_nodes: &[MessageForwardNode],
695738
recipient_node_id: PublicKey, dummy_hop_count: usize, context: MessageContext,
696-
session_priv: &SecretKey, local_node_receive_key: ReceiveAuthKey,
739+
session_priv: &SecretKey, local_node_receive_key: ReceiveAuthKey, compact_padding: bool,
697740
) -> Vec<BlindedHop> {
698741
let dummy_count = cmp::min(dummy_hop_count, MAX_DUMMY_HOPS_COUNT);
699742
let pks = intermediate_nodes
@@ -703,9 +746,8 @@ pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
703746
core::iter::repeat((recipient_node_id, Some(local_node_receive_key))).take(dummy_count),
704747
)
705748
.chain(core::iter::once((recipient_node_id, Some(local_node_receive_key))));
706-
let is_compact = intermediate_nodes.iter().any(|node| node.short_channel_id.is_some());
707749

708-
let tlvs = pks
750+
let intermediate_tlvs = pks
709751
.clone()
710752
.skip(1) // The first node's TLVs contains the next node's pubkey
711753
.zip(intermediate_nodes.iter().map(|node| node.short_channel_id))
@@ -716,18 +758,43 @@ pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
716758
.map(|next_hop| {
717759
ControlTlvs::Forward(ForwardTlvs { next_hop, next_blinding_override: None })
718760
})
719-
.chain((0..dummy_count).map(|_| ControlTlvs::Dummy))
720-
.chain(core::iter::once(ControlTlvs::Receive(ReceiveTlvs { context: Some(context) })));
721-
722-
if is_compact {
723-
let path = pks.zip(tlvs);
724-
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 (we can only go from no-TLV to a type + length
770+
// byte). Thus, if there are any intermediate hops that need to be padded by exactly one
771+
// 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+
}
725777
} else {
726-
let path =
727-
pks.zip(tlvs.map(|tlv| BlindedPathWithPadding {
728-
tlvs: tlv,
729-
round_off: MESSAGE_PADDING_ROUND_OFF,
730-
}));
731-
utils::construct_blinded_hops(secp_ctx, path, session_priv)
732-
}
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 fields to intermediate hops, so they should
788+
// never go over `MESSAGE_PADDING_ROUND_OFF`.
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)
733800
}

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/ln/channelmanager.rs

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5735,29 +5735,12 @@ where
57355735
pub fn send_payment_for_bolt12_invoice(
57365736
&self, invoice: &Bolt12Invoice, context: Option<&OffersContext>,
57375737
) -> Result<(), Bolt12PaymentError> {
5738-
match self.verify_bolt12_invoice(invoice, context) {
5738+
match self.flow.verify_bolt12_invoice(invoice, context) {
57395739
Ok(payment_id) => self.send_payment_for_verified_bolt12_invoice(invoice, payment_id),
57405740
Err(()) => Err(Bolt12PaymentError::UnexpectedInvoice),
57415741
}
57425742
}
57435743

5744-
fn verify_bolt12_invoice(
5745-
&self, invoice: &Bolt12Invoice, context: Option<&OffersContext>,
5746-
) -> Result<PaymentId, ()> {
5747-
let secp_ctx = &self.secp_ctx;
5748-
let expanded_key = &self.inbound_payment_key;
5749-
5750-
match context {
5751-
None if invoice.is_for_refund_without_paths() => {
5752-
invoice.verify_using_metadata(expanded_key, secp_ctx)
5753-
},
5754-
Some(&OffersContext::OutboundPayment { payment_id, nonce, .. }) => {
5755-
invoice.verify_using_payer_data(payment_id, nonce, expanded_key, secp_ctx)
5756-
},
5757-
_ => Err(()),
5758-
}
5759-
}
5760-
57615744
fn send_payment_for_verified_bolt12_invoice(
57625745
&self, invoice: &Bolt12Invoice, payment_id: PaymentId,
57635746
) -> Result<(), Bolt12PaymentError> {
@@ -15793,7 +15776,7 @@ where
1579315776
},
1579415777
OffersMessage::StaticInvoice(invoice) => {
1579515778
let payment_id = match context {
15796-
Some(OffersContext::OutboundPayment { payment_id, .. }) => payment_id,
15779+
Some(OffersContext::OutboundPaymentForOffer { payment_id, .. }) => payment_id,
1579715780
_ => return None
1579815781
};
1579915782
let res = self.initiate_async_payment(&invoice, payment_id);
@@ -15809,7 +15792,8 @@ where
1580915792
log_trace!(logger, "Received invoice_error: {}", invoice_error);
1581015793

1581115794
match context {
15812-
Some(OffersContext::OutboundPayment { payment_id, .. }) => {
15795+
Some(OffersContext::OutboundPaymentForOffer { payment_id, .. })
15796+
|Some(OffersContext::OutboundPaymentForRefund { payment_id, .. }) => {
1581315797
self.abandon_payment_with_reason(
1581415798
payment_id, PaymentFailureReason::InvoiceRequestRejected,
1581515799
);

0 commit comments

Comments
 (0)