Skip to content

Commit

Permalink
Use compact blinded paths for short-lived offers
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jkczyz committed May 29, 2024
1 parent 5c28c67 commit 102ffe4
Show file tree
Hide file tree
Showing 3 changed files with 193 additions and 39 deletions.
65 changes: 49 additions & 16 deletions lightning/src/ln/channelmanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1551,8 +1551,9 @@ where
/// #
/// # fn example<T: AChannelManager>(channel_manager: T) -> Result<(), Bolt12SemanticError> {
/// # let channel_manager = channel_manager.get_cm();
/// # let absolute_expiry = None;
/// let offer = channel_manager
/// .create_offer_builder()?
/// .create_offer_builder(absolute_expiry)?
/// # ;
/// # // Needed for compiling for c_bindings
/// # let builder: lightning::offers::offer::OfferBuilder<_, _> = offer.into();
Expand Down Expand Up @@ -8542,16 +8543,15 @@ where

macro_rules! create_offer_builder { ($self: ident, $builder: ty) => {
/// Creates an [`OfferBuilder`] such that the [`Offer`] it builds is recognized by the
/// [`ChannelManager`] when handling [`InvoiceRequest`] messages for the offer. The offer will
/// not have an expiration unless otherwise set on the builder.
/// [`ChannelManager`] when handling [`InvoiceRequest`] messages for the offer. The offer's
/// expiration will be `absolute_expiry` if `Some`, otherwise it will not expire.
///
/// # Privacy
///
/// Uses [`MessageRouter::create_compact_blinded_paths`] to construct a [`BlindedPath`] for the
/// offer. However, if one is not found, uses a one-hop [`BlindedPath`] with
/// [`ChannelManager::get_our_node_id`] as the introduction node instead. In the latter case,
/// the node must be announced, otherwise, there is no way to find a path to the introduction in
/// order to send the [`InvoiceRequest`].
/// Uses [`MessageRouter`] to construct a [`BlindedPath`] for the offer. However, if one is not
/// found, uses a one-hop [`BlindedPath`] with [`ChannelManager::get_our_node_id`] as the
/// introduction node instead. In the latter case, the node must be announced, otherwise, there
/// is no way to find a path to the introduction in order to send the [`InvoiceRequest`].
///
/// Also, uses a derived signing pubkey in the offer for recipient privacy.
///
Expand All @@ -8566,20 +8566,27 @@ macro_rules! create_offer_builder { ($self: ident, $builder: ty) => {
///
/// [`Offer`]: crate::offers::offer::Offer
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
pub fn create_offer_builder(&$self) -> Result<$builder, Bolt12SemanticError> {
pub fn create_offer_builder(
&$self, absolute_expiry: Option<Duration>
) -> Result<$builder, Bolt12SemanticError> {
let node_id = $self.get_our_node_id();
let expanded_key = &$self.inbound_payment_key;
let entropy = &*$self.entropy_source;
let secp_ctx = &$self.secp_ctx;

let path = $self.create_compact_blinded_path()
let path = $self.create_blinded_path_using_absolute_expiry(absolute_expiry)
.map_err(|_| Bolt12SemanticError::MissingPaths)?;
let builder = OfferBuilder::deriving_signing_pubkey(
node_id, expanded_key, entropy, secp_ctx
)
.chain_hash($self.chain_hash)
.path(path);

let builder = match absolute_expiry {
None => builder,
Some(absolute_expiry) => builder.absolute_expiry(absolute_expiry),
};

Ok(builder.into())
}
} }
Expand Down Expand Up @@ -8607,11 +8614,10 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => {
///
/// # Privacy
///
/// Uses [`MessageRouter::create_compact_blinded_paths`] to construct a [`BlindedPath`] for the
/// refund. However, if one is not found, uses a one-hop [`BlindedPath`] with
/// [`ChannelManager::get_our_node_id`] as the introduction node instead. In the latter case,
/// the node must be announced, otherwise, there is no way to find a path to the introduction in
/// order to send the [`Bolt12Invoice`].
/// Uses [`MessageRouter`] to construct a [`BlindedPath`] for the refund. However, if one is not
/// found, uses a one-hop [`BlindedPath`] with [`ChannelManager::get_our_node_id`] as the
/// introduction node instead. In the latter case, the node must be announced, otherwise, there
/// is no way to find a path to the introduction in order to send the [`Bolt12Invoice`].
///
/// Also, uses a derived payer id in the refund for payer privacy.
///
Expand Down Expand Up @@ -8640,7 +8646,7 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => {
let entropy = &*$self.entropy_source;
let secp_ctx = &$self.secp_ctx;

let path = $self.create_compact_blinded_path()
let path = $self.create_blinded_path_using_absolute_expiry(Some(absolute_expiry))
.map_err(|_| Bolt12SemanticError::MissingPaths)?;
let builder = RefundBuilder::deriving_payer_id(
node_id, expanded_key, entropy, secp_ctx, amount_msats, payment_id
Expand Down Expand Up @@ -8990,6 +8996,33 @@ where
inbound_payment::get_payment_preimage(payment_hash, payment_secret, &self.inbound_payment_key)
}

/// Creates a blinded path by delegating to [`MessageRouter`] based on the path's intended
/// lifetime.
///
/// Whether or not the path is compact depends on whether the path is short-lived or long-lived,
/// respectively, based on the given `absolute_expiry` as seconds since the Unix epoch.
fn create_blinded_path_using_absolute_expiry(
&self, absolute_expiry: Option<Duration>
) -> Result<BlindedPath, ()> {
const MAX_SHORT_LIVED_RELATIVE_EXPIRY: Duration = Duration::from_secs(60 * 60 * 24);

#[cfg(not(feature = "std"))]
let now = Duration::from_secs(
self.highest_seen_timestamp.load(Ordering::Acquire) as u64
);
#[cfg(feature = "std")]
let now = std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH");

let max_short_lived_absolute_expiry = now.saturating_add(MAX_SHORT_LIVED_RELATIVE_EXPIRY);
if absolute_expiry.unwrap_or(Duration::MAX) <= max_short_lived_absolute_expiry {
self.create_compact_blinded_path()
} else {
self.create_blinded_path()
}
}

/// Creates a blinded path by delegating to [`MessageRouter::create_blinded_paths`].
///
/// Errors if the `MessageRouter` errors or returns an empty `Vec`.
Expand Down
14 changes: 14 additions & 0 deletions lightning/src/ln/functional_test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ use core::cell::RefCell;
use core::iter::repeat;
use core::mem;
use core::ops::Deref;
use core::time::Duration;
use crate::io;
use crate::prelude::*;
use crate::sync::{Arc, Mutex, LockTestExt, RwLock};
Expand Down Expand Up @@ -464,6 +465,19 @@ impl<'a, 'b, 'c> Node<'a, 'b, 'c> {
self.override_init_features.borrow().clone()
.unwrap_or_else(|| self.node.init_features() | self.onion_messenger.provided_init_features(peer_node_id))
}

pub fn duration_since_epoch(&self) -> Duration {
#[cfg(not(feature = "std"))]
let now = Duration::from_secs(
self.node.highest_seen_timestamp.load(Ordering::Acquire) as u64
);
#[cfg(feature = "std")]
let now = std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH");

now
}
}

#[cfg(feature = "std")]
Expand Down
Loading

0 comments on commit 102ffe4

Please sign in to comment.