Skip to content

Commit 3c26e76

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 68c38bc commit 3c26e76

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(|_| ())?,
@@ -600,13 +643,23 @@ impl_writeable_tlv_based!(DNSResolverContext, {
600643
pub(crate) const MESSAGE_PADDING_ROUND_OFF: usize = 100;
601644

602645
/// Construct blinded onion message hops for the given `intermediate_nodes` and `recipient_node_id`.
603-
pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
604-
secp_ctx: &Secp256k1<T>, intermediate_nodes: &[MessageForwardNode],
605-
recipient_node_id: PublicKey, context: MessageContext, session_priv: &SecretKey,
606-
) -> Result<Vec<BlindedHop>, secp256k1::Error> {
646+
pub(super) fn blinded_hops<ES: Deref, T: secp256k1::Signing + secp256k1::Verification>(
647+
secp_ctx: &Secp256k1<T>, entropy_source: ES,
648+
expanded_key: Option<&inbound_payment::ExpandedKey>, intermediate_nodes: &[MessageForwardNode],
649+
recipient_node_id: PublicKey, context: MessageContext, dummy_hops_count: u8,
650+
session_priv: &SecretKey,
651+
) -> Result<Vec<BlindedHop>, secp256k1::Error>
652+
where
653+
ES::Target: EntropySource,
654+
{
655+
if expanded_key.is_none() && dummy_hops_count > 0 {
656+
debug_assert!(false, "Dummy hops are not supported without expanded keys");
657+
}
658+
607659
let pks = intermediate_nodes
608660
.iter()
609661
.map(|node| node.node_id)
662+
.chain((0..dummy_hops_count).map(|_| recipient_node_id))
610663
.chain(core::iter::once(recipient_node_id));
611664
let is_compact = intermediate_nodes.iter().any(|node| node.short_channel_id.is_some());
612665

@@ -621,6 +674,14 @@ pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
621674
.map(|next_hop| {
622675
ControlTlvs::Forward(ForwardTlvs { next_hop, next_blinding_override: None })
623676
})
677+
.chain((0..1).filter(|_| dummy_hops_count > 0).map(|_| {
678+
let dummy_tlv = UnauthenticatedDummyTlv {};
679+
let nonce = Nonce::from_entropy_source(&*entropy_source);
680+
let hmac = dummy_tlv.hmac_data(nonce, expanded_key.unwrap());
681+
let tlv = PrimaryDummyTlv { dummy_tlv, authentication: (hmac, nonce) };
682+
ControlTlvs::Dummy(DummyTlv::Primary(tlv))
683+
}))
684+
.chain((1..dummy_hops_count).map(|_| ControlTlvs::Dummy(DummyTlv::Subsequent)))
624685
.chain(core::iter::once(ControlTlvs::Receive(ReceiveTlvs { context: Some(context) })));
625686

626687
if is_compact {

0 commit comments

Comments
 (0)