Skip to content

Commit 8012c2b

Browse files
committed
Use compact blinded paths for short-lived offers
When an offer is short-lived, the likelihood of a channel used in a compact blinded path going away is low. Require passing the absolute expiry of an offer to ChannelManager::create_offer_builder so that it can be used to determine whether or not compact blinded path should be used. Use the same criteria for creating blinded paths for refunds as well.
1 parent 411b9b4 commit 8012c2b

File tree

2 files changed

+203
-38
lines changed

2 files changed

+203
-38
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 69 additions & 14 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,17 @@ 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_compact_blinded_paths`] to construct a [`BlindedPath`] for the
8249-
/// offer. However, if one is not found, uses a one-hop [`BlindedPath`] with
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. However, if one is not found, uses a one-hop [`BlindedPath`] with
82508265
/// [`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`].
8266+
/// the node must be announced, otherwise, there is no way to find a path to the introduction
8267+
/// node in order to send the [`InvoiceRequest`].
82538268
///
82548269
/// Also, uses a derived signing pubkey in the offer for recipient privacy.
82558270
///
@@ -8264,20 +8279,27 @@ macro_rules! create_offer_builder { ($self: ident, $builder: ty) => {
82648279
///
82658280
/// [`Offer`]: crate::offers::offer::Offer
82668281
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
8267-
pub fn create_offer_builder(&$self) -> Result<$builder, Bolt12SemanticError> {
8282+
pub fn create_offer_builder(
8283+
&$self, absolute_expiry: Option<Duration>
8284+
) -> Result<$builder, Bolt12SemanticError> {
82688285
let node_id = $self.get_our_node_id();
82698286
let expanded_key = &$self.inbound_payment_key;
82708287
let entropy = &*$self.entropy_source;
82718288
let secp_ctx = &$self.secp_ctx;
82728289

8273-
let path = $self.create_compact_blinded_path()
8290+
let path = $self.create_blinded_path_using_absolute_expiry(absolute_expiry)
82748291
.map_err(|_| Bolt12SemanticError::MissingPaths)?;
82758292
let builder = OfferBuilder::deriving_signing_pubkey(
82768293
node_id, expanded_key, entropy, secp_ctx
82778294
)
82788295
.chain_hash($self.chain_hash)
82798296
.path(path);
82808297

8298+
let builder = match absolute_expiry {
8299+
None => builder,
8300+
Some(absolute_expiry) => builder.absolute_expiry(absolute_expiry),
8301+
};
8302+
82818303
Ok(builder.into())
82828304
}
82838305
} }
@@ -8305,11 +8327,12 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => {
83058327
///
83068328
/// # Privacy
83078329
///
8308-
/// Uses [`MessageRouter::create_compact_blinded_paths`] to construct a [`BlindedPath`] for the
8309-
/// refund. However, if one is not found, uses a one-hop [`BlindedPath`] with
8330+
/// Uses [`MessageRouter`] to construct a [`BlindedPath`] for the refund based on the given
8331+
/// `absolute_expiry` according to [`MAX_SHORT_LIVED_RELATIVE_EXPIRY`]. See those docs for
8332+
/// privacy implications. However, if one is not found, uses a one-hop [`BlindedPath`] with
83108333
/// [`ChannelManager::get_our_node_id`] as the introduction node instead. In the latter case,
8311-
/// the node must be announced, otherwise, there is no way to find a path to the introduction in
8312-
/// order to send the [`Bolt12Invoice`].
8334+
/// the node must be announced, otherwise, there is no way to find a path to the introduction
8335+
/// node in order to send the [`Bolt12Invoice`].
83138336
///
83148337
/// Also, uses a derived payer id in the refund for payer privacy.
83158338
///
@@ -8338,7 +8361,7 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => {
83388361
let entropy = &*$self.entropy_source;
83398362
let secp_ctx = &$self.secp_ctx;
83408363

8341-
let path = $self.create_compact_blinded_path()
8364+
let path = $self.create_blinded_path_using_absolute_expiry(Some(absolute_expiry))
83428365
.map_err(|_| Bolt12SemanticError::MissingPaths)?;
83438366
let builder = RefundBuilder::deriving_payer_id(
83448367
node_id, expanded_key, entropy, secp_ctx, amount_msats, payment_id
@@ -8688,6 +8711,38 @@ where
86888711
inbound_payment::get_payment_preimage(payment_hash, payment_secret, &self.inbound_payment_key)
86898712
}
86908713

8714+
/// Creates a blinded path by delegating to [`MessageRouter`] based on the path's intended
8715+
/// lifetime.
8716+
///
8717+
/// Whether or not the path is compact depends on whether the path is short-lived or long-lived,
8718+
/// respectively, based on the given `absolute_expiry` as seconds since the Unix epoch. See
8719+
/// [`MAX_SHORT_LIVED_RELATIVE_EXPIRY`].
8720+
fn create_blinded_path_using_absolute_expiry(
8721+
&self, absolute_expiry: Option<Duration>
8722+
) -> Result<BlindedPath, ()> {
8723+
let now = self.duration_since_epoch();
8724+
let max_short_lived_absolute_expiry = now.saturating_add(MAX_SHORT_LIVED_RELATIVE_EXPIRY);
8725+
8726+
if absolute_expiry.unwrap_or(Duration::MAX) <= max_short_lived_absolute_expiry {
8727+
self.create_compact_blinded_path()
8728+
} else {
8729+
self.create_blinded_path()
8730+
}
8731+
}
8732+
8733+
pub(super) fn duration_since_epoch(&self) -> Duration {
8734+
#[cfg(not(feature = "std"))]
8735+
let now = Duration::from_secs(
8736+
self.highest_seen_timestamp.load(Ordering::Acquire) as u64
8737+
);
8738+
#[cfg(feature = "std")]
8739+
let now = std::time::SystemTime::now()
8740+
.duration_since(std::time::SystemTime::UNIX_EPOCH)
8741+
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH");
8742+
8743+
now
8744+
}
8745+
86918746
/// Creates a blinded path by delegating to [`MessageRouter::create_blinded_paths`].
86928747
///
86938748
/// Errors if the `MessageRouter` errors or returns an empty `Vec`.

0 commit comments

Comments
 (0)