Skip to content

Commit 2319bfe

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 4ce34ba commit 2319bfe

File tree

1 file changed

+57
-2
lines changed

1 file changed

+57
-2
lines changed

lightning/src/blinded_path/payment.rs

Lines changed: 57 additions & 2 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,
@@ -363,6 +391,7 @@ pub(crate) enum BlindedTrampolineTlvs {
363391
}
364392

365393
// Used to include forward and receive TLVs in the same iterator for encoding.
394+
#[derive(Clone)]
366395
enum BlindedPaymentTlvsRef<'a> {
367396
Forward(&'a ForwardTlvs),
368397
Dummy,
@@ -644,22 +673,48 @@ pub(crate) const PAYMENT_PADDING_ROUND_OFF: usize = 30;
644673
/// Construct blinded payment hops for the given `intermediate_nodes` and payee info.
645674
pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
646675
secp_ctx: &Secp256k1<T>, intermediate_nodes: &[PaymentForwardNode], payee_node_id: PublicKey,
647-
payee_tlvs: ReceiveTlvs, session_priv: &SecretKey, local_node_receive_key: ReceiveAuthKey,
676+
dummy_hop_count: usize, payee_tlvs: ReceiveTlvs, session_priv: &SecretKey,
677+
local_node_receive_key: ReceiveAuthKey,
648678
) -> Vec<BlindedHop> {
679+
let dummy_count = core::cmp::min(dummy_hop_count, MAX_DUMMY_HOPS_COUNT);
649680
let pks = intermediate_nodes
650681
.iter()
651682
.map(|node| (node.node_id, None))
683+
.chain(core::iter::repeat((payee_node_id, Some(local_node_receive_key))).take(dummy_count))
652684
.chain(core::iter::once((payee_node_id, Some(local_node_receive_key))));
653685
let tlvs = intermediate_nodes
654686
.iter()
655687
.map(|node| BlindedPaymentTlvsRef::Forward(&node.tlvs))
688+
.chain(core::iter::repeat(BlindedPaymentTlvsRef::Dummy).take(dummy_count))
656689
.chain(core::iter::once(BlindedPaymentTlvsRef::Receive(&payee_tlvs)));
657690

658691
let path = pks.zip(
659692
tlvs.map(|tlv| BlindedPathWithPadding { tlvs: tlv, round_off: PAYMENT_PADDING_ROUND_OFF }),
660693
);
661694

662-
utils::construct_blinded_hops(secp_ctx, path, session_priv)
695+
// Debug invariant: all non-final hops must have identical serialized size.
696+
#[cfg(debug_assertions)]
697+
{
698+
let mut iter = path.clone();
699+
if let Some((_, first)) = iter.next() {
700+
let remaining = iter.clone().count(); // includes intermediate + final
701+
702+
// At least one intermediate hop
703+
if remaining > 1 {
704+
let expected = first.serialized_length();
705+
706+
// skip final hop: take(remaining - 1)
707+
for (_, hop) in iter.take(remaining - 1) {
708+
debug_assert!(
709+
hop.serialized_length() == expected,
710+
"All intermediate blinded hops must have identical serialized size"
711+
);
712+
}
713+
}
714+
}
715+
}
716+
717+
utils::construct_blinded_hops(secp_ctx, path.into_iter(), session_priv)
663718
}
664719

665720
/// `None` if underflow occurs.

0 commit comments

Comments
 (0)