Skip to content

Commit 21d474a

Browse files
Util for blinded paths to configure an async recipient
As part of serving static invoices to payers on behalf of often-offline recipients, these recipients need a way to contact the static invoice server to retrieve blinded paths to include in their offers. Add a utility to create blinded paths for this purpose as a static invoice server. The recipient will be configured with the resulting paths and use them to request offer paths on startup.
1 parent 45bfc17 commit 21d474a

File tree

4 files changed

+110
-0
lines changed

4 files changed

+110
-0
lines changed

lightning/src/blinded_path/message.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,34 @@ pub enum OffersContext {
406406
/// [`AsyncPaymentsMessage`]: crate::onion_message::async_payments::AsyncPaymentsMessage
407407
#[derive(Clone, Debug)]
408408
pub enum AsyncPaymentsContext {
409+
/// Context used by a [`BlindedMessagePath`] that an async recipient is configured with in
410+
/// [`UserConfig::paths_to_static_invoice_server`], provided back to us in corresponding
411+
/// [`OfferPathsRequest`]s.
412+
///
413+
/// [`UserConfig::paths_to_static_invoice_server`]: crate::util::config::UserConfig::paths_to_static_invoice_server
414+
/// [`OfferPathsRequest`]: crate::onion_message::async_payments::OfferPathsRequest
415+
OfferPathsRequest {
416+
/// An identifier for the async recipient that is requesting blinded paths to include in their
417+
/// [`Offer::paths`]. This ID is intended to be included in the reply path to our [`OfferPaths`]
418+
/// response, and subsequently rate limit [`ServeStaticInvoice`] messages from recipients.
419+
///
420+
/// [`Offer::paths`]: crate::offers::offer::Offer::paths
421+
/// [`OfferPaths`]: crate::onion_message::async_payments::OfferPaths
422+
/// [`ServeStaticInvoice`]: crate::onion_message::async_payments::ServeStaticInvoice
423+
recipient_id_nonce: Nonce,
424+
/// Authentication code for the [`OfferPathsRequest`].
425+
///
426+
/// Prevents nodes from requesting offer paths from us without having been previously configured
427+
/// with a [`BlindedMessagePath`] that we generated.
428+
///
429+
/// [`OfferPathsRequest`]: crate::onion_message::async_payments::OfferPathsRequest
430+
hmac: Hmac<Sha256>,
431+
/// The time as duration since the Unix epoch at which this path expires and messages sent over
432+
/// it should be ignored.
433+
///
434+
/// Useful to timeout async recipients that are no longer supported as clients.
435+
path_absolute_expiry: core::time::Duration,
436+
},
409437
/// Context used by a reply path to an [`OfferPathsRequest`], provided back to us in corresponding
410438
/// [`OfferPaths`] messages.
411439
///
@@ -581,6 +609,11 @@ impl_writeable_tlv_based_enum!(AsyncPaymentsContext,
581609
(12, hmac, required),
582610
(14, path_absolute_expiry, required),
583611
},
612+
(4, OfferPathsRequest) => {
613+
(0, recipient_id_nonce, required),
614+
(2, hmac, required),
615+
(4, path_absolute_expiry, required),
616+
},
584617
);
585618

586619
/// Contains a simple nonce for use in a blinded path's context.

lightning/src/ln/channelmanager.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10743,6 +10743,29 @@ where
1074310743
}
1074410744

1074510745
#[cfg(any(test, async_payments))]
10746+
/// [`BlindedMessagePath`]s that an async recipient will be configured with via
10747+
/// [`UserConfig::paths_to_static_invoice_server`], enabling the recipient to request blinded
10748+
/// paths from us for inclusion in their [`Offer::paths`].
10749+
///
10750+
/// If `relative_expiry` is unset, the [`BlindedMessagePath`]s expiry will default to
10751+
/// [`DEFAULT_CONFIG_PATH_RELATIVE_EXPIRY`].
10752+
///
10753+
/// Returns the paths to be included in the recipient's
10754+
/// [`UserConfig::paths_to_static_invoice_server`] as well as a nonce that uniquely identifies the
10755+
/// recipient that has been configured with these paths. // TODO link to events that surface this nonce
10756+
///
10757+
/// [`UserConfig::paths_to_static_invoice_server`]: crate::util::config::UserConfig::paths_to_static_invoice_server
10758+
/// [`Offer::paths`]: crate::offers::offer::Offer::paths
10759+
/// [`DEFAULT_CONFIG_PATH_RELATIVE_EXPIRY`]: crate::onion_message::async_payments::DEFAULT_CONFIG_PATH_RELATIVE_EXPIRY
10760+
#[cfg(async_payments)]
10761+
pub fn blinded_paths_for_async_recipient(
10762+
&self, relative_expiry: Option<Duration>
10763+
) -> Result<(Vec<BlindedMessagePath>, Nonce), ()> {
10764+
let peers = self.get_peers_for_blinded_path();
10765+
let entropy = &*self.entropy_source;
10766+
self.flow.blinded_paths_for_async_recipient(peers, relative_expiry, entropy)
10767+
}
10768+
1074610769
pub(super) fn duration_since_epoch(&self) -> Duration {
1074710770
#[cfg(not(feature = "std"))]
1074810771
let now = Duration::from_secs(

lightning/src/offers/flow.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,43 @@ impl<MR: Deref> OffersMessageFlow<MR>
231231
where
232232
MR::Target: MessageRouter,
233233
{
234+
/// [`BlindedMessagePath`]s that an async recipient will be configured with via
235+
/// [`UserConfig::paths_to_static_invoice_server`], enabling the recipient to request blinded
236+
/// paths from us for inclusion in their [`Offer::paths`].
237+
///
238+
/// If `relative_expiry` is unset, the resulting [`BlindedMessagePath`]s will not expire.
239+
///
240+
/// Returns the paths to be included in the recipient's
241+
/// [`UserConfig::paths_to_static_invoice_server`] as well as a nonce that uniquely identifies the
242+
/// recipient that has been configured with these paths.
243+
///
244+
/// [`UserConfig::paths_to_static_invoice_server`]: crate::util::config::UserConfig::paths_to_static_invoice_server
245+
/// [`Offer::paths`]: crate::offers::offer::Offer::paths
246+
/// [`DEFAULT_CONFIG_PATH_RELATIVE_EXPIRY`]: crate::onion_message::async_payments::DEFAULT_CONFIG_PATH_RELATIVE_EXPIRY
247+
#[cfg(async_payments)]
248+
pub fn blinded_paths_for_async_recipient<ES: Deref>(
249+
&self, peers: Vec<MessageForwardNode>, relative_expiry: Option<Duration>, entropy: ES,
250+
) -> Result<(Vec<BlindedMessagePath>, Nonce), ()>
251+
where
252+
ES::Target: EntropySource,
253+
{
254+
let expanded_key = &self.inbound_payment_key;
255+
256+
let path_absolute_expiry = relative_expiry
257+
.unwrap_or(Duration::from_secs(u64::MAX))
258+
.saturating_add(self.duration_since_epoch());
259+
260+
let recipient_id_nonce = Nonce::from_entropy_source(entropy);
261+
let hmac = signer::hmac_for_offer_paths_request_context(recipient_id_nonce, expanded_key);
262+
263+
let context = MessageContext::AsyncPayments(AsyncPaymentsContext::OfferPathsRequest {
264+
recipient_id_nonce,
265+
hmac,
266+
path_absolute_expiry,
267+
});
268+
self.create_blinded_paths(peers, context).map(|paths| (paths, recipient_id_nonce))
269+
}
270+
234271
/// Creates a collection of blinded paths by delegating to [`MessageRouter`] based on
235272
/// the path's intended lifetime.
236273
///

lightning/src/offers/signer.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ const ASYNC_PAYMENTS_OFFER_PATHS_INPUT: &[u8; 16] = &[10; 16];
6565
#[cfg(async_payments)]
6666
const ASYNC_PAYMENTS_STATIC_INV_PERSISTED_INPUT: &[u8; 16] = &[11; 16];
6767

68+
///
69+
#[cfg(async_payments)]
70+
const ASYNC_PAYMENTS_OFFER_PATHS_REQUEST_INPUT: &[u8; 16] = &[12; 16];
71+
6872
/// Message metadata which possibly is derived from [`MetadataMaterial`] such that it can be
6973
/// verified.
7074
#[derive(Clone)]
@@ -581,6 +585,19 @@ pub(crate) fn verify_held_htlc_available_context(
581585
}
582586
}
583587

588+
#[cfg(async_payments)]
589+
pub(crate) fn hmac_for_offer_paths_request_context(
590+
nonce: Nonce, expanded_key: &ExpandedKey,
591+
) -> Hmac<Sha256> {
592+
const IV_BYTES: &[u8; IV_LEN] = b"LDK Paths Please"; // TODO
593+
let mut hmac = expanded_key.hmac_for_offer();
594+
hmac.input(IV_BYTES);
595+
hmac.input(&nonce.0);
596+
hmac.input(ASYNC_PAYMENTS_OFFER_PATHS_REQUEST_INPUT);
597+
598+
Hmac::from_engine(hmac)
599+
}
600+
584601
#[cfg(async_payments)]
585602
pub(crate) fn hmac_for_offer_paths_context(
586603
nonce: Nonce, expanded_key: &ExpandedKey,

0 commit comments

Comments
 (0)