Skip to content

Commit 88124a9

Browse files
Merge pull request #3080 from jkczyz/2024-05-compact-blinded-path-creation-trait
Optional compact blinded path creation
2 parents 1e49c4a + c17a026 commit 88124a9

File tree

9 files changed

+511
-134
lines changed

9 files changed

+511
-134
lines changed

fuzz/src/chanmon_consistency.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ use bitcoin::hashes::sha256d::Hash as Sha256dHash;
3434
use bitcoin::hash_types::BlockHash;
3535

3636
use lightning::blinded_path::BlindedPath;
37-
use lightning::blinded_path::message::ForwardNode;
3837
use lightning::blinded_path::payment::ReceiveTlvs;
3938
use lightning::chain;
4039
use lightning::chain::{BestBlock, ChannelMonitorUpdateStatus, chainmonitor, channelmonitor, Confirm, Watch};
@@ -124,7 +123,7 @@ impl MessageRouter for FuzzRouter {
124123
}
125124

126125
fn create_blinded_paths<T: secp256k1::Signing + secp256k1::Verification>(
127-
&self, _recipient: PublicKey, _peers: Vec<ForwardNode>, _secp_ctx: &Secp256k1<T>,
126+
&self, _recipient: PublicKey, _peers: Vec<PublicKey>, _secp_ctx: &Secp256k1<T>,
128127
) -> Result<Vec<BlindedPath>, ()> {
129128
unreachable!()
130129
}

fuzz/src/full_stack.rs

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

3333
use lightning::blinded_path::BlindedPath;
34-
use lightning::blinded_path::message::ForwardNode;
3534
use lightning::blinded_path::payment::ReceiveTlvs;
3635
use lightning::chain;
3736
use lightning::chain::{BestBlock, ChannelMonitorUpdateStatus, Confirm, Listen};
@@ -162,7 +161,7 @@ impl MessageRouter for FuzzRouter {
162161
}
163162

164163
fn create_blinded_paths<T: secp256k1::Signing + secp256k1::Verification>(
165-
&self, _recipient: PublicKey, _peers: Vec<ForwardNode>, _secp_ctx: &Secp256k1<T>,
164+
&self, _recipient: PublicKey, _peers: Vec<PublicKey>, _secp_ctx: &Secp256k1<T>,
166165
) -> Result<Vec<BlindedPath>, ()> {
167166
unreachable!()
168167
}

fuzz/src/onion_message.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ 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;
1110
use lightning::ln::features::InitFeatures;
1211
use lightning::ln::msgs::{self, DecodeError, OnionMessageHandler};
1312
use lightning::ln::script::ShutdownScript;
@@ -89,7 +88,7 @@ impl MessageRouter for TestMessageRouter {
8988
}
9089

9190
fn create_blinded_paths<T: secp256k1::Signing + secp256k1::Verification>(
92-
&self, _recipient: PublicKey, _peers: Vec<ForwardNode>, _secp_ctx: &Secp256k1<T>,
91+
&self, _recipient: PublicKey, _peers: Vec<PublicKey>, _secp_ctx: &Secp256k1<T>,
9392
) -> Result<Vec<BlindedPath>, ()> {
9493
unreachable!()
9594
}

lightning/src/ln/channelmanager.rs

Lines changed: 94 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1554,8 +1554,9 @@ where
15541554
/// #
15551555
/// # fn example<T: AChannelManager>(channel_manager: T) -> Result<(), Bolt12SemanticError> {
15561556
/// # let channel_manager = channel_manager.get_cm();
1557+
/// # let absolute_expiry = None;
15571558
/// let offer = channel_manager
1558-
/// .create_offer_builder()?
1559+
/// .create_offer_builder(absolute_expiry)?
15591560
/// # ;
15601561
/// # // Needed for compiling for c_bindings
15611562
/// # let builder: lightning::offers::offer::OfferBuilder<_, _> = offer.into();
@@ -2287,6 +2288,19 @@ const MAX_UNFUNDED_CHANNEL_PEERS: usize = 50;
22872288
/// many peers we reject new (inbound) connections.
22882289
const MAX_NO_CHANNEL_PEERS: usize = 250;
22892290

2291+
/// The maximum expiration from the current time where an [`Offer`] or [`Refund`] is considered
2292+
/// short-lived, while anything with a greater expiration is considered long-lived.
2293+
///
2294+
/// Using [`ChannelManager::create_offer_builder`] or [`ChannelManager::create_refund_builder`],
2295+
/// will included a [`BlindedPath`] created using:
2296+
/// - [`MessageRouter::create_compact_blinded_paths`] when short-lived, and
2297+
/// - [`MessageRouter::create_blinded_paths`] when long-lived.
2298+
///
2299+
/// Using compact [`BlindedPath`]s may provide better privacy as the [`MessageRouter`] could select
2300+
/// more hops. However, since they use short channel ids instead of pubkeys, they are more likely to
2301+
/// become invalid over time as channels are closed. Thus, they are only suitable for short-term use.
2302+
pub const MAX_SHORT_LIVED_RELATIVE_EXPIRY: Duration = Duration::from_secs(60 * 60 * 24);
2303+
22902304
/// Used by [`ChannelManager::list_recent_payments`] to express the status of recent payments.
22912305
/// These include payments that have yet to find a successful path, or have unresolved HTLCs.
22922306
#[derive(Debug, PartialEq)]
@@ -8240,16 +8254,15 @@ where
82408254

82418255
macro_rules! create_offer_builder { ($self: ident, $builder: ty) => {
82428256
/// Creates an [`OfferBuilder`] such that the [`Offer`] it builds is recognized by the
8243-
/// [`ChannelManager`] when handling [`InvoiceRequest`] messages for the offer. The offer will
8244-
/// not have an expiration unless otherwise set on the builder.
8257+
/// [`ChannelManager`] when handling [`InvoiceRequest`] messages for the offer. The offer's
8258+
/// expiration will be `absolute_expiry` if `Some`, otherwise it will not expire.
82458259
///
82468260
/// # Privacy
82478261
///
8248-
/// Uses [`MessageRouter::create_blinded_paths`] to construct a [`BlindedPath`] for the offer.
8249-
/// However, if one is not found, uses a one-hop [`BlindedPath`] with
8250-
/// [`ChannelManager::get_our_node_id`] as the introduction node instead. In the latter case,
8251-
/// the node must be announced, otherwise, there is no way to find a path to the introduction in
8252-
/// order to send the [`InvoiceRequest`].
8262+
/// Uses [`MessageRouter`] to construct a [`BlindedPath`] for the offer based on the given
8263+
/// `absolute_expiry` according to [`MAX_SHORT_LIVED_RELATIVE_EXPIRY`]. See those docs for
8264+
/// privacy implications as well as those of the parameterized [`Router`], which implements
8265+
/// [`MessageRouter`].
82538266
///
82548267
/// Also, uses a derived signing pubkey in the offer for recipient privacy.
82558268
///
@@ -8264,19 +8277,27 @@ macro_rules! create_offer_builder { ($self: ident, $builder: ty) => {
82648277
///
82658278
/// [`Offer`]: crate::offers::offer::Offer
82668279
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
8267-
pub fn create_offer_builder(&$self) -> Result<$builder, Bolt12SemanticError> {
8280+
pub fn create_offer_builder(
8281+
&$self, absolute_expiry: Option<Duration>
8282+
) -> Result<$builder, Bolt12SemanticError> {
82688283
let node_id = $self.get_our_node_id();
82698284
let expanded_key = &$self.inbound_payment_key;
82708285
let entropy = &*$self.entropy_source;
82718286
let secp_ctx = &$self.secp_ctx;
82728287

8273-
let path = $self.create_blinded_path().map_err(|_| Bolt12SemanticError::MissingPaths)?;
8288+
let path = $self.create_blinded_path_using_absolute_expiry(absolute_expiry)
8289+
.map_err(|_| Bolt12SemanticError::MissingPaths)?;
82748290
let builder = OfferBuilder::deriving_signing_pubkey(
82758291
node_id, expanded_key, entropy, secp_ctx
82768292
)
82778293
.chain_hash($self.chain_hash)
82788294
.path(path);
82798295

8296+
let builder = match absolute_expiry {
8297+
None => builder,
8298+
Some(absolute_expiry) => builder.absolute_expiry(absolute_expiry),
8299+
};
8300+
82808301
Ok(builder.into())
82818302
}
82828303
} }
@@ -8304,11 +8325,10 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => {
83048325
///
83058326
/// # Privacy
83068327
///
8307-
/// Uses [`MessageRouter::create_blinded_paths`] to construct a [`BlindedPath`] for the refund.
8308-
/// However, if one is not found, uses a one-hop [`BlindedPath`] with
8309-
/// [`ChannelManager::get_our_node_id`] as the introduction node instead. In the latter case,
8310-
/// the node must be announced, otherwise, there is no way to find a path to the introduction in
8311-
/// order to send the [`Bolt12Invoice`].
8328+
/// Uses [`MessageRouter`] to construct a [`BlindedPath`] for the refund based on the given
8329+
/// `absolute_expiry` according to [`MAX_SHORT_LIVED_RELATIVE_EXPIRY`]. See those docs for
8330+
/// privacy implications as well as those of the parameterized [`Router`], which implements
8331+
/// [`MessageRouter`].
83128332
///
83138333
/// Also, uses a derived payer id in the refund for payer privacy.
83148334
///
@@ -8337,7 +8357,8 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => {
83378357
let entropy = &*$self.entropy_source;
83388358
let secp_ctx = &$self.secp_ctx;
83398359

8340-
let path = $self.create_blinded_path().map_err(|_| Bolt12SemanticError::MissingPaths)?;
8360+
let path = $self.create_blinded_path_using_absolute_expiry(Some(absolute_expiry))
8361+
.map_err(|_| Bolt12SemanticError::MissingPaths)?;
83418362
let builder = RefundBuilder::deriving_payer_id(
83428363
node_id, expanded_key, entropy, secp_ctx, amount_msats, payment_id
83438364
)?
@@ -8406,10 +8427,9 @@ where
84068427
///
84078428
/// # Privacy
84088429
///
8409-
/// Uses a one-hop [`BlindedPath`] for the reply path with [`ChannelManager::get_our_node_id`]
8410-
/// as the introduction node and a derived payer id for payer privacy. As such, currently, the
8411-
/// node must be announced. Otherwise, there is no way to find a path to the introduction node
8412-
/// in order to send the [`Bolt12Invoice`].
8430+
/// For payer privacy, uses a derived payer id and uses [`MessageRouter::create_blinded_paths`]
8431+
/// to construct a [`BlindedPath`] for the reply path. For further privacy implications, see the
8432+
/// docs of the parameterized [`Router`], which implements [`MessageRouter`].
84138433
///
84148434
/// # Limitations
84158435
///
@@ -8686,6 +8706,38 @@ where
86868706
inbound_payment::get_payment_preimage(payment_hash, payment_secret, &self.inbound_payment_key)
86878707
}
86888708

8709+
/// Creates a blinded path by delegating to [`MessageRouter`] based on the path's intended
8710+
/// lifetime.
8711+
///
8712+
/// Whether or not the path is compact depends on whether the path is short-lived or long-lived,
8713+
/// respectively, based on the given `absolute_expiry` as seconds since the Unix epoch. See
8714+
/// [`MAX_SHORT_LIVED_RELATIVE_EXPIRY`].
8715+
fn create_blinded_path_using_absolute_expiry(
8716+
&self, absolute_expiry: Option<Duration>
8717+
) -> Result<BlindedPath, ()> {
8718+
let now = self.duration_since_epoch();
8719+
let max_short_lived_absolute_expiry = now.saturating_add(MAX_SHORT_LIVED_RELATIVE_EXPIRY);
8720+
8721+
if absolute_expiry.unwrap_or(Duration::MAX) <= max_short_lived_absolute_expiry {
8722+
self.create_compact_blinded_path()
8723+
} else {
8724+
self.create_blinded_path()
8725+
}
8726+
}
8727+
8728+
pub(super) fn duration_since_epoch(&self) -> Duration {
8729+
#[cfg(not(feature = "std"))]
8730+
let now = Duration::from_secs(
8731+
self.highest_seen_timestamp.load(Ordering::Acquire) as u64
8732+
);
8733+
#[cfg(feature = "std")]
8734+
let now = std::time::SystemTime::now()
8735+
.duration_since(std::time::SystemTime::UNIX_EPOCH)
8736+
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH");
8737+
8738+
now
8739+
}
8740+
86898741
/// Creates a blinded path by delegating to [`MessageRouter::create_blinded_paths`].
86908742
///
86918743
/// Errors if the `MessageRouter` errors or returns an empty `Vec`.
@@ -8696,6 +8748,27 @@ where
86968748
let peers = self.per_peer_state.read().unwrap()
86978749
.iter()
86988750
.map(|(node_id, peer_state)| (node_id, peer_state.lock().unwrap()))
8751+
.filter(|(_, peer)| peer.is_connected)
8752+
.filter(|(_, peer)| peer.latest_features.supports_onion_messages())
8753+
.map(|(node_id, _)| *node_id)
8754+
.collect::<Vec<_>>();
8755+
8756+
self.router
8757+
.create_blinded_paths(recipient, peers, secp_ctx)
8758+
.and_then(|paths| paths.into_iter().next().ok_or(()))
8759+
}
8760+
8761+
/// Creates a blinded path by delegating to [`MessageRouter::create_compact_blinded_paths`].
8762+
///
8763+
/// Errors if the `MessageRouter` errors or returns an empty `Vec`.
8764+
fn create_compact_blinded_path(&self) -> Result<BlindedPath, ()> {
8765+
let recipient = self.get_our_node_id();
8766+
let secp_ctx = &self.secp_ctx;
8767+
8768+
let peers = self.per_peer_state.read().unwrap()
8769+
.iter()
8770+
.map(|(node_id, peer_state)| (node_id, peer_state.lock().unwrap()))
8771+
.filter(|(_, peer)| peer.is_connected)
86998772
.filter(|(_, peer)| peer.latest_features.supports_onion_messages())
87008773
.map(|(node_id, peer)| ForwardNode {
87018774
node_id: *node_id,
@@ -8708,7 +8781,7 @@ where
87088781
.collect::<Vec<_>>();
87098782

87108783
self.router
8711-
.create_blinded_paths(recipient, peers, secp_ctx)
8784+
.create_compact_blinded_paths(recipient, peers, secp_ctx)
87128785
.and_then(|paths| paths.into_iter().next().ok_or(()))
87138786
}
87148787

lightning/src/ln/functional_test_utils.rs

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3259,30 +3259,34 @@ pub fn create_network<'a, 'b: 'a, 'c: 'b>(node_count: usize, cfgs: &'b Vec<NodeC
32593259

32603260
for i in 0..node_count {
32613261
for j in (i+1)..node_count {
3262-
let node_id_i = nodes[i].node.get_our_node_id();
3263-
let node_id_j = nodes[j].node.get_our_node_id();
3264-
3265-
let init_i = msgs::Init {
3266-
features: nodes[i].init_features(&node_id_j),
3267-
networks: None,
3268-
remote_network_address: None,
3269-
};
3270-
let init_j = msgs::Init {
3271-
features: nodes[j].init_features(&node_id_i),
3272-
networks: None,
3273-
remote_network_address: None,
3274-
};
3275-
3276-
nodes[i].node.peer_connected(&node_id_j, &init_j, true).unwrap();
3277-
nodes[j].node.peer_connected(&node_id_i, &init_i, false).unwrap();
3278-
nodes[i].onion_messenger.peer_connected(&node_id_j, &init_j, true).unwrap();
3279-
nodes[j].onion_messenger.peer_connected(&node_id_i, &init_i, false).unwrap();
3262+
connect_nodes(&nodes[i], &nodes[j]);
32803263
}
32813264
}
32823265

32833266
nodes
32843267
}
32853268

3269+
fn connect_nodes<'a, 'b: 'a, 'c: 'b>(node_a: &Node<'a, 'b, 'c>, node_b: &Node<'a, 'b, 'c>) {
3270+
let node_id_a = node_a.node.get_our_node_id();
3271+
let node_id_b = node_b.node.get_our_node_id();
3272+
3273+
let init_a = msgs::Init {
3274+
features: node_a.init_features(&node_id_b),
3275+
networks: None,
3276+
remote_network_address: None,
3277+
};
3278+
let init_b = msgs::Init {
3279+
features: node_b.init_features(&node_id_a),
3280+
networks: None,
3281+
remote_network_address: None,
3282+
};
3283+
3284+
node_a.node.peer_connected(&node_id_b, &init_b, true).unwrap();
3285+
node_b.node.peer_connected(&node_id_a, &init_a, false).unwrap();
3286+
node_a.onion_messenger.peer_connected(&node_id_b, &init_b, true).unwrap();
3287+
node_b.onion_messenger.peer_connected(&node_id_a, &init_a, false).unwrap();
3288+
}
3289+
32863290
pub fn connect_dummy_node<'a, 'b: 'a, 'c: 'b>(node: &Node<'a, 'b, 'c>) {
32873291
let node_id_dummy = PublicKey::from_slice(&[2; 33]).unwrap();
32883292

@@ -3643,13 +3647,8 @@ pub fn reconnect_nodes<'a, 'b, 'c, 'd>(args: ReconnectArgs<'a, 'b, 'c, 'd>) {
36433647
pending_cell_htlc_claims, pending_cell_htlc_fails, pending_raa,
36443648
pending_responding_commitment_signed, pending_responding_commitment_signed_dup_monitor,
36453649
} = args;
3646-
node_a.node.peer_connected(&node_b.node.get_our_node_id(), &msgs::Init {
3647-
features: node_b.node.init_features(), networks: None, remote_network_address: None
3648-
}, true).unwrap();
3650+
connect_nodes(node_a, node_b);
36493651
let reestablish_1 = get_chan_reestablish_msgs!(node_a, node_b);
3650-
node_b.node.peer_connected(&node_a.node.get_our_node_id(), &msgs::Init {
3651-
features: node_a.node.init_features(), networks: None, remote_network_address: None
3652-
}, false).unwrap();
36533652
let reestablish_2 = get_chan_reestablish_msgs!(node_b, node_a);
36543653

36553654
if send_channel_ready.0 {

0 commit comments

Comments
 (0)