Skip to content

Commit 71918d0

Browse files
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. Co-authored-by: valentinewallace <[email protected]>
1 parent 8692dfd commit 71918d0

File tree

1 file changed

+64
-5
lines changed

1 file changed

+64
-5
lines changed

lightning/src/blinded_path/message.rs

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@ impl BlindedMessagePath {
6969
/// pubkey in `node_pks` will be the destination node.
7070
///
7171
/// Errors if no hops are provided or if `node_pk`(s) are invalid.
72-
// TODO: make all payloads the same size with padding + add dummy hops
7372
pub fn new<ES: Deref, T: secp256k1::Signing + secp256k1::Verification>(
7473
intermediate_nodes: &[MessageForwardNode], recipient_node_id: PublicKey,
7574
context: MessageContext, entropy_source: ES, secp_ctx: &Secp256k1<T>,
@@ -89,9 +88,53 @@ impl BlindedMessagePath {
8988
blinding_point: PublicKey::from_secret_key(secp_ctx, &blinding_secret),
9089
blinded_hops: blinded_hops(
9190
secp_ctx,
91+
entropy_source,
92+
None,
9293
intermediate_nodes,
9394
recipient_node_id,
9495
context,
96+
0,
97+
&blinding_secret,
98+
)
99+
.map_err(|_| ())?,
100+
}))
101+
}
102+
103+
/// Create a path for an onion message, to be forwarded along `node_pks`.
104+
///
105+
/// Additionally allows appending a number of dummy hops before the final hop,
106+
/// increasing the total path length and enhancing privacy by obscuring the true
107+
/// distance between sender and recipient.
108+
///
109+
/// The last node pubkey in `node_pks` will be the destination node.
110+
///
111+
/// Errors if no hops are provided or if `node_pk`(s) are invalid.
112+
pub fn new_with_dummy_hops<ES: Deref, T: secp256k1::Signing + secp256k1::Verification>(
113+
intermediate_nodes: &[MessageForwardNode], dummy_hops_count: u8,
114+
recipient_node_id: PublicKey, context: MessageContext, entropy_source: ES,
115+
expanded_key: &inbound_payment::ExpandedKey, secp_ctx: &Secp256k1<T>,
116+
) -> Result<Self, ()>
117+
where
118+
ES::Target: EntropySource,
119+
{
120+
let introduction_node = IntroductionNode::NodeId(
121+
intermediate_nodes.first().map_or(recipient_node_id, |n| n.node_id),
122+
);
123+
let blinding_secret_bytes = entropy_source.get_secure_random_bytes();
124+
let blinding_secret =
125+
SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted");
126+
127+
Ok(Self(BlindedPath {
128+
introduction_node,
129+
blinding_point: PublicKey::from_secret_key(secp_ctx, &blinding_secret),
130+
blinded_hops: blinded_hops(
131+
secp_ctx,
132+
entropy_source,
133+
Some(expanded_key),
134+
intermediate_nodes,
135+
recipient_node_id,
136+
context,
137+
dummy_hops_count,
95138
&blinding_secret,
96139
)
97140
.map_err(|_| ())?,
@@ -563,13 +606,23 @@ impl_writeable_tlv_based!(DNSResolverContext, {
563606
pub(crate) const MESSAGE_PADDING_ROUND_OFF: usize = 100;
564607

565608
/// Construct blinded onion message hops for the given `intermediate_nodes` and `recipient_node_id`.
566-
pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
567-
secp_ctx: &Secp256k1<T>, intermediate_nodes: &[MessageForwardNode],
568-
recipient_node_id: PublicKey, context: MessageContext, session_priv: &SecretKey,
569-
) -> Result<Vec<BlindedHop>, secp256k1::Error> {
609+
pub(super) fn blinded_hops<ES: Deref, T: secp256k1::Signing + secp256k1::Verification>(
610+
secp_ctx: &Secp256k1<T>, entropy_source: ES,
611+
expanded_key: Option<&inbound_payment::ExpandedKey>, intermediate_nodes: &[MessageForwardNode],
612+
recipient_node_id: PublicKey, context: MessageContext, dummy_hops_count: u8,
613+
session_priv: &SecretKey,
614+
) -> Result<Vec<BlindedHop>, secp256k1::Error>
615+
where
616+
ES::Target: EntropySource,
617+
{
618+
if expanded_key.is_none() && dummy_hops_count > 0 {
619+
debug_assert!(false, "Dummy hops are not supported without expanded keys");
620+
}
621+
570622
let pks = intermediate_nodes
571623
.iter()
572624
.map(|node| node.node_id)
625+
.chain((0..dummy_hops_count).map(|_| recipient_node_id))
573626
.chain(core::iter::once(recipient_node_id));
574627
let is_compact = intermediate_nodes.iter().any(|node| node.short_channel_id.is_some());
575628

@@ -584,6 +637,12 @@ pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
584637
.map(|next_hop| {
585638
ControlTlvs::Forward(ForwardTlvs { next_hop, next_blinding_override: None })
586639
})
640+
.chain((0..dummy_hops_count).map(|_| {
641+
let dummy_tlv = UnauthenticatedDummyTlv {};
642+
let nonce = Nonce::from_entropy_source(&*entropy_source);
643+
let hmac = dummy_tlv.hmac_data(nonce, expanded_key.unwrap());
644+
ControlTlvs::Dummy(DummyTlv { dummy_tlv, authentication: (hmac, nonce) })
645+
}))
587646
.chain(core::iter::once(ControlTlvs::Receive(ReceiveTlvs { context: Some(context) })));
588647

589648
if is_compact {

0 commit comments

Comments
 (0)