Skip to content

Commit df01208

Browse files
authored
Merge pull request #3011 from jkczyz/2024-04-compact-blinded-path-creation
Compact blinded path creation
2 parents 2c71923 + e4661fe commit df01208

16 files changed

+236
-64
lines changed

fuzz/src/chanmon_consistency.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ use bitcoin::hashes::sha256d::Hash as Sha256dHash;
3131
use bitcoin::hash_types::{BlockHash, WPubkeyHash};
3232

3333
use lightning::blinded_path::BlindedPath;
34+
use lightning::blinded_path::message::ForwardNode;
3435
use lightning::blinded_path::payment::ReceiveTlvs;
3536
use lightning::chain;
3637
use lightning::chain::{BestBlock, ChannelMonitorUpdateStatus, chainmonitor, channelmonitor, Confirm, Watch};
@@ -119,7 +120,7 @@ impl MessageRouter for FuzzRouter {
119120
}
120121

121122
fn create_blinded_paths<T: secp256k1::Signing + secp256k1::Verification>(
122-
&self, _recipient: PublicKey, _peers: Vec<PublicKey>, _secp_ctx: &Secp256k1<T>,
123+
&self, _recipient: PublicKey, _peers: Vec<ForwardNode>, _secp_ctx: &Secp256k1<T>,
123124
) -> Result<Vec<BlindedPath>, ()> {
124125
unreachable!()
125126
}

fuzz/src/full_stack.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ use bitcoin::hashes::sha256d::Hash as Sha256dHash;
2828
use bitcoin::hash_types::{Txid, BlockHash, WPubkeyHash};
2929

3030
use lightning::blinded_path::BlindedPath;
31+
use lightning::blinded_path::message::ForwardNode;
3132
use lightning::blinded_path::payment::ReceiveTlvs;
3233
use lightning::chain;
3334
use lightning::chain::{BestBlock, ChannelMonitorUpdateStatus, Confirm, Listen};
@@ -157,7 +158,7 @@ impl MessageRouter for FuzzRouter {
157158
}
158159

159160
fn create_blinded_paths<T: secp256k1::Signing + secp256k1::Verification>(
160-
&self, _recipient: PublicKey, _peers: Vec<PublicKey>, _secp_ctx: &Secp256k1<T>,
161+
&self, _recipient: PublicKey, _peers: Vec<ForwardNode>, _secp_ctx: &Secp256k1<T>,
161162
) -> Result<Vec<BlindedPath>, ()> {
162163
unreachable!()
163164
}

fuzz/src/invoice_request_deser.rs

+13-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use bitcoin::secp256k1::{KeyPair, Parity, PublicKey, Secp256k1, SecretKey, self}
1111
use crate::utils::test_logger;
1212
use core::convert::TryFrom;
1313
use lightning::blinded_path::BlindedPath;
14+
use lightning::blinded_path::message::ForwardNode;
1415
use lightning::sign::EntropySource;
1516
use lightning::ln::PaymentHash;
1617
use lightning::ln::features::BlindedHopFeatures;
@@ -73,9 +74,19 @@ fn build_response<T: secp256k1::Signing + secp256k1::Verification>(
7374
invoice_request: &InvoiceRequest, secp_ctx: &Secp256k1<T>
7475
) -> Result<UnsignedBolt12Invoice, Bolt12SemanticError> {
7576
let entropy_source = Randomness {};
77+
let intermediate_nodes = [
78+
[
79+
ForwardNode { node_id: pubkey(43), short_channel_id: None },
80+
ForwardNode { node_id: pubkey(44), short_channel_id: None },
81+
],
82+
[
83+
ForwardNode { node_id: pubkey(45), short_channel_id: None },
84+
ForwardNode { node_id: pubkey(46), short_channel_id: None },
85+
],
86+
];
7687
let paths = vec![
77-
BlindedPath::new_for_message(&[pubkey(43), pubkey(44), pubkey(42)], &entropy_source, secp_ctx).unwrap(),
78-
BlindedPath::new_for_message(&[pubkey(45), pubkey(46), pubkey(42)], &entropy_source, secp_ctx).unwrap(),
88+
BlindedPath::new_for_message(&intermediate_nodes[0], pubkey(42), &entropy_source, secp_ctx).unwrap(),
89+
BlindedPath::new_for_message(&intermediate_nodes[1], pubkey(42), &entropy_source, secp_ctx).unwrap(),
7990
];
8091

8192
let payinfo = vec![

fuzz/src/onion_message.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use bitcoin::secp256k1::ecdsa::RecoverableSignature;
77
use bitcoin::secp256k1::schnorr;
88

99
use lightning::blinded_path::{BlindedPath, EmptyNodeIdLookUp};
10+
use lightning::blinded_path::message::ForwardNode;
1011
use lightning::ln::features::InitFeatures;
1112
use lightning::ln::msgs::{self, DecodeError, OnionMessageHandler};
1213
use lightning::ln::script::ShutdownScript;
@@ -88,7 +89,7 @@ impl MessageRouter for TestMessageRouter {
8889
}
8990

9091
fn create_blinded_paths<T: secp256k1::Signing + secp256k1::Verification>(
91-
&self, _recipient: PublicKey, _peers: Vec<PublicKey>, _secp_ctx: &Secp256k1<T>,
92+
&self, _recipient: PublicKey, _peers: Vec<ForwardNode>, _secp_ctx: &Secp256k1<T>,
9293
) -> Result<Vec<BlindedPath>, ()> {
9394
unreachable!()
9495
}

fuzz/src/refund_deser.rs

+13-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey, self};
1111
use crate::utils::test_logger;
1212
use core::convert::TryFrom;
1313
use lightning::blinded_path::BlindedPath;
14+
use lightning::blinded_path::message::ForwardNode;
1415
use lightning::sign::EntropySource;
1516
use lightning::ln::PaymentHash;
1617
use lightning::ln::features::BlindedHopFeatures;
@@ -62,9 +63,19 @@ fn build_response<T: secp256k1::Signing + secp256k1::Verification>(
6263
refund: &Refund, signing_pubkey: PublicKey, secp_ctx: &Secp256k1<T>
6364
) -> Result<UnsignedBolt12Invoice, Bolt12SemanticError> {
6465
let entropy_source = Randomness {};
66+
let intermediate_nodes = [
67+
[
68+
ForwardNode { node_id: pubkey(43), short_channel_id: None },
69+
ForwardNode { node_id: pubkey(44), short_channel_id: None },
70+
],
71+
[
72+
ForwardNode { node_id: pubkey(45), short_channel_id: None },
73+
ForwardNode { node_id: pubkey(46), short_channel_id: None },
74+
],
75+
];
6576
let paths = vec![
66-
BlindedPath::new_for_message(&[pubkey(43), pubkey(44), pubkey(42)], &entropy_source, secp_ctx).unwrap(),
67-
BlindedPath::new_for_message(&[pubkey(45), pubkey(46), pubkey(42)], &entropy_source, secp_ctx).unwrap(),
77+
BlindedPath::new_for_message(&intermediate_nodes[0], pubkey(42), &entropy_source, secp_ctx).unwrap(),
78+
BlindedPath::new_for_message(&intermediate_nodes[1], pubkey(42), &entropy_source, secp_ctx).unwrap(),
6879
];
6980

7081
let payinfo = vec![

lightning/src/blinded_path/message.rs

+37-6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
// This file is Copyright its original authors, visible in version control
2+
// history.
3+
//
4+
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
5+
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
7+
// You may not use this file except in accordance with one or both of these
8+
// licenses.
9+
10+
//! Data structures and methods for constructing [`BlindedPath`]s to send a message over.
11+
//!
12+
//! [`BlindedPath`]: crate::blinded_path::BlindedPath
13+
114
use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey};
215

316
#[allow(unused_imports)]
@@ -16,6 +29,17 @@ use crate::util::ser::{FixedLengthReader, LengthReadableArgs, Writeable, Writer}
1629
use core::mem;
1730
use core::ops::Deref;
1831

32+
/// An intermediate node, and possibly a short channel id leading to the next node.
33+
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
34+
pub struct ForwardNode {
35+
/// This node's pubkey.
36+
pub node_id: PublicKey,
37+
/// The channel between `node_id` and the next hop. If set, the constructed [`BlindedHop`]'s
38+
/// `encrypted_payload` will use this instead of the next [`ForwardNode::node_id`] for a more
39+
/// compact representation.
40+
pub short_channel_id: Option<u64>,
41+
}
42+
1943
/// TLVs to encode in an intermediate onion message packet's hop data. When provided in a blinded
2044
/// route, they are encoded into [`BlindedHop::encrypted_payload`].
2145
pub(crate) struct ForwardTlvs {
@@ -60,17 +84,24 @@ impl Writeable for ReceiveTlvs {
6084
}
6185
}
6286

63-
/// Construct blinded onion message hops for the given `unblinded_path`.
87+
/// Construct blinded onion message hops for the given `intermediate_nodes` and `recipient_node_id`.
6488
pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
65-
secp_ctx: &Secp256k1<T>, unblinded_path: &[PublicKey], session_priv: &SecretKey
89+
secp_ctx: &Secp256k1<T>, intermediate_nodes: &[ForwardNode], recipient_node_id: PublicKey,
90+
session_priv: &SecretKey
6691
) -> Result<Vec<BlindedHop>, secp256k1::Error> {
67-
let blinded_tlvs = unblinded_path.iter()
92+
let pks = intermediate_nodes.iter().map(|node| &node.node_id)
93+
.chain(core::iter::once(&recipient_node_id));
94+
let tlvs = pks.clone()
6895
.skip(1) // The first node's TLVs contains the next node's pubkey
69-
.map(|pk| ForwardTlvs { next_hop: NextMessageHop::NodeId(*pk), next_blinding_override: None })
70-
.map(|tlvs| ControlTlvs::Forward(tlvs))
96+
.zip(intermediate_nodes.iter().map(|node| node.short_channel_id))
97+
.map(|(pubkey, scid)| match scid {
98+
Some(scid) => NextMessageHop::ShortChannelId(scid),
99+
None => NextMessageHop::NodeId(*pubkey),
100+
})
101+
.map(|next_hop| ControlTlvs::Forward(ForwardTlvs { next_hop, next_blinding_override: None }))
71102
.chain(core::iter::once(ControlTlvs::Receive(ReceiveTlvs { path_id: None })));
72103

73-
utils::construct_blinded_hops(secp_ctx, unblinded_path.iter(), blinded_tlvs, session_priv)
104+
utils::construct_blinded_hops(secp_ctx, pks, tlvs, session_priv)
74105
}
75106

76107
// Advance the blinded onion message path by one hop, so make the second hop into the new

lightning/src/blinded_path/mod.rs

+40-6
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
//! Creating blinded paths and related utilities live here.
1111
1212
pub mod payment;
13-
pub(crate) mod message;
13+
pub mod message;
1414
pub(crate) mod utils;
1515

1616
use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey};
@@ -21,6 +21,7 @@ use crate::offers::invoice::BlindedPayInfo;
2121
use crate::routing::gossip::{NodeId, ReadOnlyNetworkGraph};
2222
use crate::sign::EntropySource;
2323
use crate::util::ser::{Readable, Writeable, Writer};
24+
use crate::util::scid_utils;
2425

2526
use crate::io;
2627
use crate::prelude::*;
@@ -124,7 +125,7 @@ impl BlindedPath {
124125
pub fn one_hop_for_message<ES: Deref, T: secp256k1::Signing + secp256k1::Verification>(
125126
recipient_node_id: PublicKey, entropy_source: ES, secp_ctx: &Secp256k1<T>
126127
) -> Result<Self, ()> where ES::Target: EntropySource {
127-
Self::new_for_message(&[recipient_node_id], entropy_source, secp_ctx)
128+
Self::new_for_message(&[], recipient_node_id, entropy_source, secp_ctx)
128129
}
129130

130131
/// Create a blinded path for an onion message, to be forwarded along `node_pks`. The last node
@@ -133,17 +134,21 @@ impl BlindedPath {
133134
/// Errors if no hops are provided or if `node_pk`(s) are invalid.
134135
// TODO: make all payloads the same size with padding + add dummy hops
135136
pub fn new_for_message<ES: Deref, T: secp256k1::Signing + secp256k1::Verification>(
136-
node_pks: &[PublicKey], entropy_source: ES, secp_ctx: &Secp256k1<T>
137+
intermediate_nodes: &[message::ForwardNode], recipient_node_id: PublicKey,
138+
entropy_source: ES, secp_ctx: &Secp256k1<T>
137139
) -> Result<Self, ()> where ES::Target: EntropySource {
138-
if node_pks.is_empty() { return Err(()) }
140+
let introduction_node = IntroductionNode::NodeId(
141+
intermediate_nodes.first().map_or(recipient_node_id, |n| n.node_id)
142+
);
139143
let blinding_secret_bytes = entropy_source.get_secure_random_bytes();
140144
let blinding_secret = SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted");
141-
let introduction_node = IntroductionNode::NodeId(node_pks[0]);
142145

143146
Ok(BlindedPath {
144147
introduction_node,
145148
blinding_point: PublicKey::from_secret_key(secp_ctx, &blinding_secret),
146-
blinded_hops: message::blinded_hops(secp_ctx, node_pks, &blinding_secret).map_err(|_| ())?,
149+
blinded_hops: message::blinded_hops(
150+
secp_ctx, intermediate_nodes, recipient_node_id, &blinding_secret,
151+
).map_err(|_| ())?,
147152
})
148153
}
149154

@@ -213,6 +218,35 @@ impl BlindedPath {
213218
},
214219
}
215220
}
221+
222+
/// Attempts to a use a compact representation for the [`IntroductionNode`] by using a directed
223+
/// short channel id from a channel in `network_graph` leading to the introduction node.
224+
///
225+
/// While this may result in a smaller encoding, there is a trade off in that the path may
226+
/// become invalid if the channel is closed or hasn't been propagated via gossip. Therefore,
227+
/// calling this may not be suitable for long-lived blinded paths.
228+
pub fn use_compact_introduction_node(&mut self, network_graph: &ReadOnlyNetworkGraph) {
229+
if let IntroductionNode::NodeId(pubkey) = &self.introduction_node {
230+
let node_id = NodeId::from_pubkey(pubkey);
231+
if let Some(node_info) = network_graph.node(&node_id) {
232+
if let Some((scid, channel_info)) = node_info
233+
.channels
234+
.iter()
235+
.filter_map(|scid| network_graph.channel(*scid).map(|info| (*scid, info)))
236+
.min_by_key(|(scid, _)| scid_utils::block_from_scid(*scid))
237+
{
238+
let direction = if node_id == channel_info.node_one {
239+
Direction::NodeOne
240+
} else {
241+
debug_assert_eq!(node_id, channel_info.node_two);
242+
Direction::NodeTwo
243+
};
244+
self.introduction_node =
245+
IntroductionNode::DirectedShortChannelId(direction, scid);
246+
}
247+
}
248+
}
249+
}
216250
}
217251

218252
impl Writeable for BlindedPath {

lightning/src/blinded_path/payment.rs

+9
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
// This file is Copyright its original authors, visible in version control
2+
// history.
3+
//
4+
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
5+
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
7+
// You may not use this file except in accordance with one or both of these
8+
// licenses.
9+
110
//! Data structures and methods for constructing [`BlindedPath`]s to send a payment over.
211
//!
312
//! [`BlindedPath`]: crate::blinded_path::BlindedPath

lightning/src/ln/channel.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1403,7 +1403,7 @@ pub(super) struct ChannelContext<SP: Deref> where SP::Target: SignerProvider {
14031403
/// Either the height at which this channel was created or the height at which it was last
14041404
/// serialized if it was serialized by versions prior to 0.0.103.
14051405
/// We use this to close if funding is never broadcasted.
1406-
channel_creation_height: u32,
1406+
pub(super) channel_creation_height: u32,
14071407

14081408
counterparty_dust_limit_satoshis: u64,
14091409

lightning/src/ln/channelmanager.rs

+11-2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ use bitcoin::secp256k1::Secp256k1;
3232
use bitcoin::{secp256k1, Sequence};
3333

3434
use crate::blinded_path::{BlindedPath, NodeIdLookUp};
35+
use crate::blinded_path::message::ForwardNode;
3536
use crate::blinded_path::payment::{Bolt12OfferContext, Bolt12RefundContext, PaymentConstraints, PaymentContext, ReceiveTlvs};
3637
use crate::chain;
3738
use crate::chain::{Confirm, ChannelMonitorUpdateStatus, Watch, BestBlock};
@@ -8996,8 +8997,16 @@ where
89968997

89978998
let peers = self.per_peer_state.read().unwrap()
89988999
.iter()
8999-
.filter(|(_, peer)| peer.lock().unwrap().latest_features.supports_onion_messages())
9000-
.map(|(node_id, _)| *node_id)
9000+
.map(|(node_id, peer_state)| (node_id, peer_state.lock().unwrap()))
9001+
.filter(|(_, peer)| peer.latest_features.supports_onion_messages())
9002+
.map(|(node_id, peer)| ForwardNode {
9003+
node_id: *node_id,
9004+
short_channel_id: peer.channel_by_id
9005+
.iter()
9006+
.filter(|(_, channel)| channel.context().is_usable())
9007+
.min_by_key(|(_, channel)| channel.context().channel_creation_height)
9008+
.and_then(|(_, channel)| channel.context().get_short_channel_id()),
9009+
})
90019010
.collect::<Vec<_>>();
90029011

90039012
self.router

0 commit comments

Comments
 (0)