Skip to content

Commit d4c12db

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 3ec4df5 commit d4c12db

File tree

1 file changed

+66
-5
lines changed

1 file changed

+66
-5
lines changed

lightning/src/blinded_path/message.rs

Lines changed: 66 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(|_| ())?,
@@ -594,13 +637,23 @@ impl_writeable_tlv_based!(DNSResolverContext, {
594637
pub(crate) const MESSAGE_PADDING_ROUND_OFF: usize = 100;
595638

596639
/// Construct blinded onion message hops for the given `intermediate_nodes` and `recipient_node_id`.
597-
pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
598-
secp_ctx: &Secp256k1<T>, intermediate_nodes: &[MessageForwardNode],
599-
recipient_node_id: PublicKey, context: MessageContext, session_priv: &SecretKey,
600-
) -> Result<Vec<BlindedHop>, secp256k1::Error> {
640+
pub(super) fn blinded_hops<ES: Deref, T: secp256k1::Signing + secp256k1::Verification>(
641+
secp_ctx: &Secp256k1<T>, entropy_source: ES,
642+
expanded_key: Option<&inbound_payment::ExpandedKey>, intermediate_nodes: &[MessageForwardNode],
643+
recipient_node_id: PublicKey, context: MessageContext, dummy_hops_count: u8,
644+
session_priv: &SecretKey,
645+
) -> Result<Vec<BlindedHop>, secp256k1::Error>
646+
where
647+
ES::Target: EntropySource,
648+
{
649+
if expanded_key.is_none() && dummy_hops_count > 0 {
650+
debug_assert!(false, "Dummy hops are not supported without expanded keys");
651+
}
652+
601653
let pks = intermediate_nodes
602654
.iter()
603655
.map(|node| node.node_id)
656+
.chain((0..dummy_hops_count).map(|_| recipient_node_id))
604657
.chain(core::iter::once(recipient_node_id));
605658
let is_compact = intermediate_nodes.iter().any(|node| node.short_channel_id.is_some());
606659

@@ -615,6 +668,14 @@ pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
615668
.map(|next_hop| {
616669
ControlTlvs::Forward(ForwardTlvs { next_hop, next_blinding_override: None })
617670
})
671+
.chain((0..1).filter(|_| dummy_hops_count > 0).map(|_| {
672+
let dummy_tlv = UnauthenticatedDummyTlv {};
673+
let nonce = Nonce::from_entropy_source(&*entropy_source);
674+
let hmac = dummy_tlv.hmac_data(nonce, expanded_key.unwrap());
675+
let tlv = PrimaryDummyTlv { dummy_tlv, authentication: (hmac, nonce) };
676+
ControlTlvs::Dummy(DummyTlv::Primary(tlv))
677+
}))
678+
.chain((1..dummy_hops_count).map(|_| ControlTlvs::Dummy(DummyTlv::Subsequent)))
618679
.chain(core::iter::once(ControlTlvs::Receive(ReceiveTlvs { context: Some(context) })));
619680

620681
if is_compact {

0 commit comments

Comments
 (0)