Skip to content

Commit fe11112

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 457e951 commit fe11112

File tree

4 files changed

+125
-0
lines changed

4 files changed

+125
-0
lines changed

lightning/src/blinded_path/message.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,33 @@ 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 the static invoice server in
411+
/// corresponding [`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 will be surfaced when the async recipient eventually sends a
418+
/// corresponding [`ServeStaticInvoice`] message, and can be used to rate limit the recipient.
419+
///
420+
/// [`Offer::paths`]: crate::offers::offer::Offer::paths
421+
/// [`ServeStaticInvoice`]: crate::onion_message::async_payments::ServeStaticInvoice
422+
recipient_id_nonce: Nonce,
423+
/// Authentication code for the [`OfferPathsRequest`].
424+
///
425+
/// Prevents nodes from requesting offer paths from the static invoice server without having
426+
/// been previously configured with a [`BlindedMessagePath`] that the server generated.
427+
///
428+
/// [`OfferPathsRequest`]: crate::onion_message::async_payments::OfferPathsRequest
429+
hmac: Hmac<Sha256>,
430+
/// The time as duration since the Unix epoch at which this path expires and messages sent over
431+
/// it should be ignored.
432+
///
433+
/// Useful to timeout async recipients that are no longer supported as clients.
434+
path_absolute_expiry: core::time::Duration,
435+
},
409436
/// Context used by a reply path to an [`OfferPathsRequest`], provided back to us as an async
410437
/// recipient in corresponding [`OfferPaths`] messages from the static invoice server.
411438
///
@@ -581,6 +608,11 @@ impl_writeable_tlv_based_enum!(AsyncPaymentsContext,
581608
(12, hmac, required),
582609
(14, path_absolute_expiry, required),
583610
},
611+
(4, OfferPathsRequest) => {
612+
(0, recipient_id_nonce, required),
613+
(2, hmac, required),
614+
(4, path_absolute_expiry, required),
615+
},
584616
);
585617

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

lightning/src/ln/channelmanager.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11013,6 +11013,36 @@ where
1101311013
inbound_payment::get_payment_preimage(payment_hash, payment_secret, &self.inbound_payment_key)
1101411014
}
1101511015

11016+
/// [`BlindedMessagePath`]s for an async recipient to communicate with this node and interactively
11017+
/// build [`Offer`]s and [`StaticInvoice`]s for receiving async payments.
11018+
///
11019+
/// ## Usage
11020+
/// 1. Static invoice server calls [`Self::blinded_paths_for_async_recipient`]
11021+
/// 2. Static invoice server communicates the resulting paths out-of-band to the async recipient,
11022+
/// who includes these paths in their [`UserConfig::paths_to_static_invoice_server`]
11023+
/// 3. Async recipient automatically sends [`OfferPathsRequest`]s over the configured paths, and
11024+
/// uses the resulting paths from the server's [`OfferPaths`] response to build their async
11025+
/// receive offer
11026+
///
11027+
/// If `relative_expiry` is unset, the [`BlindedMessagePath`]s expiry will default to
11028+
/// [`DEFAULT_CONFIG_PATH_RELATIVE_EXPIRY`].
11029+
///
11030+
/// Returns the paths to be included in the recipient's
11031+
/// [`UserConfig::paths_to_static_invoice_server`] as well as a nonce that uniquely identifies the
11032+
/// recipient that has been configured with these paths. // TODO link to events that surface this nonce
11033+
///
11034+
/// [`UserConfig::paths_to_static_invoice_server`]: crate::util::config::UserConfig::paths_to_static_invoice_server
11035+
/// [`Offer::paths`]: crate::offers::offer::Offer::paths
11036+
/// [`DEFAULT_CONFIG_PATH_RELATIVE_EXPIRY`]: crate::onion_message::async_payments::DEFAULT_CONFIG_PATH_RELATIVE_EXPIRY
11037+
#[cfg(async_payments)]
11038+
pub fn blinded_paths_for_async_recipient(
11039+
&self, relative_expiry: Option<Duration>,
11040+
) -> Result<(Vec<BlindedMessagePath>, Nonce), ()> {
11041+
let peers = self.get_peers_for_blinded_path();
11042+
let entropy = &*self.entropy_source;
11043+
self.flow.blinded_paths_for_async_recipient(peers, relative_expiry, entropy)
11044+
}
11045+
1101611046
#[cfg(any(test, async_payments))]
1101711047
#[rustfmt::skip]
1101811048
pub(super) fn duration_since_epoch(&self) -> Duration {

lightning/src/offers/flow.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,51 @@ impl<MR: Deref> OffersMessageFlow<MR>
232232
where
233233
MR::Target: MessageRouter,
234234
{
235+
/// [`BlindedMessagePath`]s for an async recipient to communicate with this node and interactively
236+
/// build [`Offer`]s and [`StaticInvoice`]s for receiving async payments.
237+
///
238+
/// ## Usage
239+
/// 1. Static invoice server calls [`Self::blinded_paths_for_async_recipient`]
240+
/// 2. Static invoice server communicates the resulting paths out-of-band to the async recipient,
241+
/// who includes these paths in their [`UserConfig::paths_to_static_invoice_server`]
242+
/// 3. Async recipient automatically sends [`OfferPathsRequest`]s to the server over the
243+
/// configured paths, and uses the paths from the server's [`OfferPaths`] response to build
244+
/// their async receive offer
245+
///
246+
/// If `relative_expiry` is unset, the [`BlindedMessagePath`]s expiry will default to
247+
/// [`DEFAULT_CONFIG_PATH_RELATIVE_EXPIRY`].
248+
///
249+
/// Returns the paths to be included in the recipient's
250+
/// [`UserConfig::paths_to_static_invoice_server`] as well as a nonce that uniquely identifies the
251+
/// recipient that has been configured with these paths. // TODO link to events that surface this nonce
252+
///
253+
/// [`UserConfig::paths_to_static_invoice_server`]: crate::util::config::UserConfig::paths_to_static_invoice_server
254+
/// [`Offer::paths`]: crate::offers::offer::Offer::paths
255+
/// [`DEFAULT_CONFIG_PATH_RELATIVE_EXPIRY`]: crate::onion_message::async_payments::DEFAULT_CONFIG_PATH_RELATIVE_EXPIRY
256+
#[cfg(async_payments)]
257+
pub fn blinded_paths_for_async_recipient<ES: Deref>(
258+
&self, peers: Vec<MessageForwardNode>, relative_expiry: Option<Duration>, entropy: ES,
259+
) -> Result<(Vec<BlindedMessagePath>, Nonce), ()>
260+
where
261+
ES::Target: EntropySource,
262+
{
263+
let expanded_key = &self.inbound_payment_key;
264+
265+
let path_absolute_expiry = relative_expiry
266+
.unwrap_or(Duration::from_secs(u64::MAX))
267+
.saturating_add(self.duration_since_epoch());
268+
269+
let recipient_id_nonce = Nonce::from_entropy_source(entropy);
270+
let hmac = signer::hmac_for_offer_paths_request_context(recipient_id_nonce, expanded_key);
271+
272+
let context = MessageContext::AsyncPayments(AsyncPaymentsContext::OfferPathsRequest {
273+
recipient_id_nonce,
274+
hmac,
275+
path_absolute_expiry,
276+
});
277+
self.create_blinded_paths(peers, context).map(|paths| (paths, recipient_id_nonce))
278+
}
279+
235280
/// Creates a collection of blinded paths by delegating to [`MessageRouter`] based on
236281
/// the path's intended lifetime.
237282
///

lightning/src/offers/signer.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ 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+
/// HMAC input used in `AsyncPaymentsContext::OfferPathsRequest` to authenticate inbound
69+
/// offer_paths_request onion messages.
70+
#[cfg(async_payments)]
71+
const ASYNC_PAYMENTS_OFFER_PATHS_REQUEST_INPUT: &[u8; 16] = &[12; 16];
72+
6873
/// Message metadata which possibly is derived from [`MetadataMaterial`] such that it can be
6974
/// verified.
7075
#[derive(Clone)]
@@ -581,6 +586,19 @@ pub(crate) fn verify_held_htlc_available_context(
581586
}
582587
}
583588

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

0 commit comments

Comments
 (0)