Skip to content

Commit d2ccc93

Browse files
committed
Introduce Dummy Hop support in Blinded Path Constructor
Adds a new constructor for blinded paths that allows specifying the number of dummy hops. This enables users to insert arbitrary hops before the real destination, enhancing privacy by making it harder to infer the sender–receiver distance or identify the final destination. Lays the groundwork for future use of dummy hops in blinded path construction.
1 parent e5b172c commit d2ccc93

File tree

1 file changed

+57
-5
lines changed

1 file changed

+57
-5
lines changed

lightning/src/blinded_path/payment.rs

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use bitcoin::secp256k1::ecdh::SharedSecret;
1313
use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey};
1414

15+
use crate::blinded_path::message::MAX_DUMMY_HOPS_COUNT;
1516
use crate::blinded_path::utils::{self, BlindedPathWithPadding};
1617
use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode, NodeIdLookUp};
1718
use crate::crypto::streams::ChaChaDualPolyReadAdapter;
@@ -121,6 +122,32 @@ impl BlindedPaymentPath {
121122
local_node_receive_key: ReceiveAuthKey, payee_tlvs: ReceiveTlvs, htlc_maximum_msat: u64,
122123
min_final_cltv_expiry_delta: u16, entropy_source: ES, secp_ctx: &Secp256k1<T>,
123124
) -> Result<Self, ()>
125+
where
126+
ES::Target: EntropySource,
127+
{
128+
BlindedPaymentPath::new_with_dummy_hops(
129+
intermediate_nodes,
130+
payee_node_id,
131+
0,
132+
local_node_receive_key,
133+
payee_tlvs,
134+
htlc_maximum_msat,
135+
min_final_cltv_expiry_delta,
136+
entropy_source,
137+
secp_ctx,
138+
)
139+
}
140+
141+
/// Same as [`BlindedPaymentPath::new`], but allows specifying a number of dummy hops.
142+
///
143+
/// Note:
144+
/// At most [`MAX_DUMMY_HOPS_COUNT`] dummy hops can be added to the blinded path.
145+
pub fn new_with_dummy_hops<ES: Deref, T: secp256k1::Signing + secp256k1::Verification>(
146+
intermediate_nodes: &[PaymentForwardNode], payee_node_id: PublicKey,
147+
dummy_hop_count: usize, local_node_receive_key: ReceiveAuthKey, payee_tlvs: ReceiveTlvs,
148+
htlc_maximum_msat: u64, min_final_cltv_expiry_delta: u16, entropy_source: ES,
149+
secp_ctx: &Secp256k1<T>,
150+
) -> Result<Self, ()>
124151
where
125152
ES::Target: EntropySource,
126153
{
@@ -145,6 +172,7 @@ impl BlindedPaymentPath {
145172
secp_ctx,
146173
intermediate_nodes,
147174
payee_node_id,
175+
dummy_hop_count,
148176
payee_tlvs,
149177
&blinding_secret,
150178
local_node_receive_key,
@@ -644,22 +672,46 @@ pub(crate) const PAYMENT_PADDING_ROUND_OFF: usize = 30;
644672
/// Construct blinded payment hops for the given `intermediate_nodes` and payee info.
645673
pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
646674
secp_ctx: &Secp256k1<T>, intermediate_nodes: &[PaymentForwardNode], payee_node_id: PublicKey,
647-
payee_tlvs: ReceiveTlvs, session_priv: &SecretKey, local_node_receive_key: ReceiveAuthKey,
675+
dummy_hop_count: usize, payee_tlvs: ReceiveTlvs, session_priv: &SecretKey,
676+
local_node_receive_key: ReceiveAuthKey,
648677
) -> Vec<BlindedHop> {
678+
let dummy_count = core::cmp::min(dummy_hop_count, MAX_DUMMY_HOPS_COUNT);
649679
let pks = intermediate_nodes
650680
.iter()
651681
.map(|node| (node.node_id, None))
682+
.chain(core::iter::repeat((payee_node_id, Some(local_node_receive_key))).take(dummy_count))
652683
.chain(core::iter::once((payee_node_id, Some(local_node_receive_key))));
653684
let tlvs = intermediate_nodes
654685
.iter()
655686
.map(|node| BlindedPaymentTlvsRef::Forward(&node.tlvs))
687+
.chain((0..dummy_count).map(|_| BlindedPaymentTlvsRef::Dummy))
656688
.chain(core::iter::once(BlindedPaymentTlvsRef::Receive(&payee_tlvs)));
657689

658-
let path = pks.zip(
659-
tlvs.map(|tlv| BlindedPathWithPadding { tlvs: tlv, round_off: PAYMENT_PADDING_ROUND_OFF }),
660-
);
690+
let path: Vec<_> = pks
691+
.zip(
692+
tlvs.map(|tlv| BlindedPathWithPadding {
693+
tlvs: tlv,
694+
round_off: PAYMENT_PADDING_ROUND_OFF,
695+
}),
696+
)
697+
.collect();
698+
699+
// Debug invariant: all non-final hops must have identical serialized size.
700+
#[cfg(debug_assertions)]
701+
if let Some((_, first)) = path.first() {
702+
if path.len() > 2 {
703+
let expected = first.serialized_length();
704+
705+
for (_, hop) in path.iter().skip(1).take(path.len() - 2) {
706+
debug_assert!(
707+
hop.serialized_length() == expected,
708+
"All intermediate blinded hops must have identical serialized size"
709+
);
710+
}
711+
}
712+
}
661713

662-
utils::construct_blinded_hops(secp_ctx, path, session_priv)
714+
utils::construct_blinded_hops(secp_ctx, path.into_iter(), session_priv)
663715
}
664716

665717
/// `None` if underflow occurs.

0 commit comments

Comments
 (0)