lightning/blinded_path/
message.rs

1// This file is Copyright its original authors, visible in version control
2// history.
3//
4// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
5// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
7// You may not use this file except in accordance with one or both of these
8// licenses.
9
10//! Data structures and methods for constructing [`BlindedMessagePath`]s to send a message over.
11
12use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey};
13
14#[allow(unused_imports)]
15use crate::prelude::*;
16
17use crate::blinded_path::utils::{self, BlindedPathWithPadding};
18use crate::blinded_path::{BlindedHop, BlindedPath, Direction, IntroductionNode, NodeIdLookUp};
19use crate::crypto::streams::ChaChaPolyReadAdapter;
20use crate::io;
21use crate::io::Cursor;
22use crate::ln::channelmanager::{InterceptId, PaymentId};
23use crate::ln::msgs::DecodeError;
24use crate::ln::onion_utils;
25use crate::offers::nonce::Nonce;
26use crate::offers::offer::OfferId;
27use crate::onion_message::packet::ControlTlvs;
28use crate::routing::gossip::{NodeId, ReadOnlyNetworkGraph};
29use crate::sign::{EntropySource, NodeSigner, ReceiveAuthKey, Recipient};
30use crate::types::payment::PaymentHash;
31use crate::util::scid_utils;
32use crate::util::ser::{FixedLengthReader, LengthReadableArgs, Readable, Writeable, Writer};
33
34use core::ops::Deref;
35use core::time::Duration;
36use core::{cmp, mem};
37
38/// A blinded path to be used for sending or receiving a message, hiding the identity of the
39/// recipient.
40#[derive(Clone, Debug, Hash, PartialEq, Eq)]
41pub struct BlindedMessagePath(pub(super) BlindedPath);
42
43impl Writeable for BlindedMessagePath {
44	fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
45		self.0.write(w)
46	}
47}
48
49impl Readable for BlindedMessagePath {
50	fn read<R: io::Read>(r: &mut R) -> Result<Self, DecodeError> {
51		Ok(Self(BlindedPath::read(r)?))
52	}
53}
54
55impl BlindedMessagePath {
56	/// Create a one-hop blinded path for a message.
57	pub fn one_hop<ES: Deref, T: secp256k1::Signing + secp256k1::Verification>(
58		recipient_node_id: PublicKey, local_node_receive_key: ReceiveAuthKey,
59		context: MessageContext, entropy_source: ES, secp_ctx: &Secp256k1<T>,
60	) -> Self
61	where
62		ES::Target: EntropySource,
63	{
64		Self::new(&[], recipient_node_id, local_node_receive_key, context, entropy_source, secp_ctx)
65	}
66
67	/// Create a path for an onion message, to be forwarded along `node_pks`.
68	pub fn new<ES: Deref, T: secp256k1::Signing + secp256k1::Verification>(
69		intermediate_nodes: &[MessageForwardNode], recipient_node_id: PublicKey,
70		local_node_receive_key: ReceiveAuthKey, context: MessageContext, entropy_source: ES,
71		secp_ctx: &Secp256k1<T>,
72	) -> Self
73	where
74		ES::Target: EntropySource,
75	{
76		BlindedMessagePath::new_with_dummy_hops(
77			intermediate_nodes,
78			recipient_node_id,
79			0,
80			local_node_receive_key,
81			context,
82			entropy_source,
83			secp_ctx,
84		)
85	}
86
87	/// Same as [`BlindedMessagePath::new`], but allows specifying a number of dummy hops.
88	///
89	/// Note:
90	/// At most [`MAX_DUMMY_HOPS_COUNT`] dummy hops can be added to the blinded path.
91	pub fn new_with_dummy_hops<ES: Deref, T: secp256k1::Signing + secp256k1::Verification>(
92		intermediate_nodes: &[MessageForwardNode], recipient_node_id: PublicKey,
93		dummy_hop_count: usize, local_node_receive_key: ReceiveAuthKey, context: MessageContext,
94		entropy_source: ES, secp_ctx: &Secp256k1<T>,
95	) -> Self
96	where
97		ES::Target: EntropySource,
98	{
99		let introduction_node = IntroductionNode::NodeId(
100			intermediate_nodes.first().map_or(recipient_node_id, |n| n.node_id),
101		);
102		let blinding_secret_bytes = entropy_source.get_secure_random_bytes();
103		let blinding_secret =
104			SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted");
105
106		Self(BlindedPath {
107			introduction_node,
108			blinding_point: PublicKey::from_secret_key(secp_ctx, &blinding_secret),
109			blinded_hops: blinded_hops(
110				secp_ctx,
111				intermediate_nodes,
112				recipient_node_id,
113				dummy_hop_count,
114				context,
115				&blinding_secret,
116				local_node_receive_key,
117			),
118		})
119	}
120
121	/// Attempts to a use a compact representation for the [`IntroductionNode`] by using a directed
122	/// short channel id from a channel in `network_graph` leading to the introduction node.
123	///
124	/// While this may result in a smaller encoding, there is a trade off in that the path may
125	/// become invalid if the channel is closed or hasn't been propagated via gossip. Therefore,
126	/// calling this may not be suitable for long-lived blinded paths.
127	pub fn use_compact_introduction_node(&mut self, network_graph: &ReadOnlyNetworkGraph) {
128		if let IntroductionNode::NodeId(pubkey) = &self.0.introduction_node {
129			let node_id = NodeId::from_pubkey(pubkey);
130			if let Some(node_info) = network_graph.node(&node_id) {
131				if let Some((scid, channel_info)) = node_info
132					.channels
133					.iter()
134					.filter_map(|scid| network_graph.channel(*scid).map(|info| (*scid, info)))
135					.min_by_key(|(scid, _)| scid_utils::block_from_scid(*scid))
136				{
137					let direction = if node_id == channel_info.node_one {
138						Direction::NodeOne
139					} else {
140						debug_assert_eq!(node_id, channel_info.node_two);
141						Direction::NodeTwo
142					};
143					self.0.introduction_node =
144						IntroductionNode::DirectedShortChannelId(direction, scid);
145				}
146			}
147		}
148	}
149
150	/// Returns the introduction [`NodeId`] of the blinded path, if it is publicly reachable (i.e.,
151	/// it is found in the network graph).
152	pub fn public_introduction_node_id<'a>(
153		&self, network_graph: &'a ReadOnlyNetworkGraph,
154	) -> Option<&'a NodeId> {
155		self.0.public_introduction_node_id(network_graph)
156	}
157
158	/// The [`IntroductionNode`] of the blinded path.
159	pub fn introduction_node(&self) -> &IntroductionNode {
160		&self.0.introduction_node
161	}
162
163	/// Used by the [`IntroductionNode`] to decrypt its [`encrypted_payload`] to forward the message.
164	///
165	/// [`encrypted_payload`]: BlindedHop::encrypted_payload
166	pub fn blinding_point(&self) -> PublicKey {
167		self.0.blinding_point
168	}
169
170	/// The [`BlindedHop`]s within the blinded path.
171	pub fn blinded_hops(&self) -> &[BlindedHop] {
172		&self.0.blinded_hops
173	}
174
175	/// Advance the blinded onion message path by one hop, making the second hop into the new
176	/// introduction node.
177	///
178	/// Will only modify `self` when returning `Ok`.
179	pub fn advance_path_by_one<NS: Deref, NL: Deref, T>(
180		&mut self, node_signer: &NS, node_id_lookup: &NL, secp_ctx: &Secp256k1<T>,
181	) -> Result<(), ()>
182	where
183		NS::Target: NodeSigner,
184		NL::Target: NodeIdLookUp,
185		T: secp256k1::Signing + secp256k1::Verification,
186	{
187		let control_tlvs_ss = node_signer.ecdh(Recipient::Node, &self.0.blinding_point, None)?;
188		let rho = onion_utils::gen_rho_from_shared_secret(&control_tlvs_ss.secret_bytes());
189		let encrypted_control_tlvs = &self.0.blinded_hops.get(0).ok_or(())?.encrypted_payload;
190		let mut s = Cursor::new(encrypted_control_tlvs);
191		let mut reader = FixedLengthReader::new(&mut s, encrypted_control_tlvs.len() as u64);
192		match ChaChaPolyReadAdapter::read(&mut reader, rho) {
193			Ok(ChaChaPolyReadAdapter {
194				readable: ControlTlvs::Forward(ForwardTlvs { next_hop, next_blinding_override }),
195			}) => {
196				let next_node_id = match next_hop {
197					NextMessageHop::NodeId(pubkey) => pubkey,
198					NextMessageHop::ShortChannelId(scid) => match node_id_lookup.next_node_id(scid)
199					{
200						Some(pubkey) => pubkey,
201						None => return Err(()),
202					},
203				};
204				let mut new_blinding_point = match next_blinding_override {
205					Some(blinding_point) => blinding_point,
206					None => onion_utils::next_hop_pubkey(
207						secp_ctx,
208						self.0.blinding_point,
209						control_tlvs_ss.as_ref(),
210					)
211					.map_err(|_| ())?,
212				};
213				mem::swap(&mut self.0.blinding_point, &mut new_blinding_point);
214				self.0.introduction_node = IntroductionNode::NodeId(next_node_id);
215				self.0.blinded_hops.remove(0);
216				Ok(())
217			},
218			_ => Err(()),
219		}
220	}
221
222	pub(crate) fn introduction_node_mut(&mut self) -> &mut IntroductionNode {
223		&mut self.0.introduction_node
224	}
225
226	/// Creates a new [`BlindedMessagePath`] from its constituent parts.
227	///
228	/// Useful when you need to reconstruct a blinded path from previously serialized components.
229	///
230	/// Parameters:
231	/// * `introduction_node_id`: The public key of the introduction node in the path
232	/// * `blinding_point`: The public key used for blinding the path
233	/// * `blinded_hops`: The encrypted routing information for each hop in the path
234	pub fn from_blinded_path(
235		introduction_node_id: PublicKey, blinding_point: PublicKey, blinded_hops: Vec<BlindedHop>,
236	) -> Self {
237		Self(BlindedPath {
238			introduction_node: IntroductionNode::NodeId(introduction_node_id),
239			blinding_point,
240			blinded_hops,
241		})
242	}
243
244	#[cfg(test)]
245	pub fn clear_blinded_hops(&mut self) {
246		self.0.blinded_hops.clear()
247	}
248}
249
250/// The next hop to forward an onion message along its path.
251///
252/// Note that payment blinded paths always specify their next hop using an explicit node id.
253#[derive(Clone, Debug, Hash, PartialEq, Eq)]
254pub enum NextMessageHop {
255	/// The node id of the next hop.
256	NodeId(PublicKey),
257	/// The short channel id leading to the next hop.
258	ShortChannelId(u64),
259}
260
261/// An intermediate node, and possibly a short channel id leading to the next node.
262///
263/// Note:
264/// [`MessageForwardNode`] must represent a node that supports [`supports_onion_messages`]
265/// in order to be included in valid blinded paths for onion messaging.
266///
267/// [`supports_onion_messages`]: crate::types::features::Features::supports_onion_messages
268#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
269pub struct MessageForwardNode {
270	/// This node's pubkey.
271	pub node_id: PublicKey,
272	/// The channel between `node_id` and the next hop. If set, the constructed [`BlindedHop`]'s
273	/// `encrypted_payload` will use this instead of the next [`MessageForwardNode::node_id`] for a
274	/// more compact representation.
275	pub short_channel_id: Option<u64>,
276}
277
278/// TLVs to encode in an intermediate onion message packet's hop data. When provided in a blinded
279/// route, they are encoded into [`BlindedHop::encrypted_payload`].
280pub(crate) struct ForwardTlvs {
281	/// The next hop in the onion message's path.
282	pub(crate) next_hop: NextMessageHop,
283	/// Senders to a blinded path use this value to concatenate the route they find to the
284	/// introduction node with the blinded path.
285	pub(crate) next_blinding_override: Option<PublicKey>,
286}
287
288/// Represents the dummy TLV encoded immediately before the actual [`ReceiveTlvs`] in a blinded path.
289/// These TLVs are intended for the final node and are recursively authenticated until the real
290/// [`ReceiveTlvs`] is reached.
291///
292/// Their purpose is to arbitrarily extend the path length, obscuring the receiver's position in the
293/// route and thereby enhancing privacy.
294pub(crate) struct DummyTlv;
295
296impl Writeable for DummyTlv {
297	fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
298		encode_tlv_stream!(writer, {
299			(65539, (), required),
300		});
301		Ok(())
302	}
303}
304
305/// Similar to [`ForwardTlvs`], but these TLVs are for the final node.
306pub(crate) struct ReceiveTlvs {
307	/// If `context` is `Some`, it is used to identify the blinded path that this onion message is
308	/// sending to. This is useful for receivers to check that said blinded path is being used in
309	/// the right context.
310	pub context: Option<MessageContext>,
311}
312
313impl Writeable for ForwardTlvs {
314	fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
315		let (next_node_id, short_channel_id) = match self.next_hop {
316			NextMessageHop::NodeId(pubkey) => (Some(pubkey), None),
317			NextMessageHop::ShortChannelId(scid) => (None, Some(scid)),
318		};
319		encode_tlv_stream!(writer, {
320			(2, short_channel_id, option),
321			(4, next_node_id, option),
322			(8, self.next_blinding_override, option)
323		});
324		Ok(())
325	}
326}
327
328impl Writeable for ReceiveTlvs {
329	fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
330		encode_tlv_stream!(writer, {
331			(65537, self.context, option),
332		});
333		Ok(())
334	}
335}
336
337/// Additional data included by the recipient in a [`BlindedMessagePath`].
338///
339/// This data is encrypted by the recipient and will be given to the corresponding message handler
340/// when handling a message sent over the [`BlindedMessagePath`]. The recipient can use this data to
341/// authenticate the message or for further processing if needed.
342#[derive(Clone, Debug)]
343pub enum MessageContext {
344	/// Context specific to an [`OffersMessage`].
345	///
346	/// [`OffersMessage`]: crate::onion_message::offers::OffersMessage
347	Offers(OffersContext),
348	/// Context specific to an [`AsyncPaymentsMessage`].
349	///
350	/// [`AsyncPaymentsMessage`]: crate::onion_message::async_payments::AsyncPaymentsMessage
351	AsyncPayments(AsyncPaymentsContext),
352	/// Represents a context for a blinded path used in a reply path when requesting a DNSSEC proof
353	/// in a [`DNSResolverMessage`].
354	///
355	/// [`DNSResolverMessage`]: crate::onion_message::dns_resolution::DNSResolverMessage
356	DNSResolver(DNSResolverContext),
357	/// Context specific to a [`CustomOnionMessageHandler::CustomMessage`].
358	///
359	/// [`CustomOnionMessageHandler::CustomMessage`]: crate::onion_message::messenger::CustomOnionMessageHandler::CustomMessage
360	Custom(Vec<u8>),
361}
362
363/// Contains data specific to an [`OffersMessage`].
364///
365/// [`OffersMessage`]: crate::onion_message::offers::OffersMessage
366#[derive(Clone, Debug, Eq, PartialEq)]
367pub enum OffersContext {
368	/// Context used by a [`BlindedMessagePath`] within an [`Offer`].
369	///
370	/// This variant is intended to be received when handling an [`InvoiceRequest`].
371	///
372	/// [`Offer`]: crate::offers::offer::Offer
373	/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
374	InvoiceRequest {
375		/// A nonce used for authenticating that an [`InvoiceRequest`] is for a valid [`Offer`] and
376		/// for deriving the offer's signing keys.
377		///
378		/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
379		/// [`Offer`]: crate::offers::offer::Offer
380		nonce: Nonce,
381	},
382	/// Context used by a [`BlindedMessagePath`] within the [`Offer`] of an async recipient.
383	///
384	/// This variant is received by the static invoice server when handling an [`InvoiceRequest`] on
385	/// behalf of said async recipient.
386	///
387	/// [`Offer`]: crate::offers::offer::Offer
388	/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
389	StaticInvoiceRequested {
390		/// An identifier for the async recipient for whom we as a static invoice server are serving
391		/// [`StaticInvoice`]s. Used paired with the
392		/// [`OffersContext::StaticInvoiceRequested::invoice_slot`] when looking up a corresponding
393		/// [`StaticInvoice`] to return to the payer if the recipient is offline. This id was previously
394		/// provided via [`AsyncPaymentsContext::ServeStaticInvoice::recipient_id`].
395		///
396		/// Also useful for rate limiting the number of [`InvoiceRequest`]s we will respond to on
397		/// recipient's behalf.
398		///
399		/// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice
400		/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
401		recipient_id: Vec<u8>,
402
403		/// The slot number for a specific [`StaticInvoice`] that the recipient previously
404		/// requested be served on their behalf. Useful when paired with the
405		/// [`OffersContext::StaticInvoiceRequested::recipient_id`] to pull that specific invoice from
406		/// the database when payers send an [`InvoiceRequest`]. This id was previously
407		/// provided via [`AsyncPaymentsContext::ServeStaticInvoice::invoice_slot`].
408		///
409		/// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice
410		/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
411		invoice_slot: u16,
412
413		/// The time as duration since the Unix epoch at which this path expires and messages sent over
414		/// it should be ignored.
415		///
416		/// Useful to timeout async recipients that are no longer supported as clients.
417		path_absolute_expiry: Duration,
418	},
419	/// Context used by a [`BlindedMessagePath`] within a [`Refund`] or as a reply path for an
420	/// [`InvoiceRequest`].
421	///
422	/// This variant is intended to be received when handling a [`Bolt12Invoice`] or an
423	/// [`InvoiceError`].
424	///
425	/// [`Refund`]: crate::offers::refund::Refund
426	/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
427	/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
428	/// [`InvoiceError`]: crate::offers::invoice_error::InvoiceError
429	OutboundPayment {
430		/// Payment ID used when creating a [`Refund`] or [`InvoiceRequest`].
431		///
432		/// [`Refund`]: crate::offers::refund::Refund
433		/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
434		payment_id: PaymentId,
435
436		/// A nonce used for authenticating that a [`Bolt12Invoice`] is for a valid [`Refund`] or
437		/// [`InvoiceRequest`] and for deriving their signing keys.
438		///
439		/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
440		/// [`Refund`]: crate::offers::refund::Refund
441		/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
442		nonce: Nonce,
443	},
444	/// Context used by a [`BlindedMessagePath`] as a reply path for a [`Bolt12Invoice`].
445	///
446	/// This variant is intended to be received when handling an [`InvoiceError`].
447	///
448	/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
449	/// [`InvoiceError`]: crate::offers::invoice_error::InvoiceError
450	InboundPayment {
451		/// The same payment hash as [`Bolt12Invoice::payment_hash`].
452		///
453		/// [`Bolt12Invoice::payment_hash`]: crate::offers::invoice::Bolt12Invoice::payment_hash
454		payment_hash: PaymentHash,
455	},
456}
457
458/// Contains data specific to an [`AsyncPaymentsMessage`].
459///
460/// [`AsyncPaymentsMessage`]: crate::onion_message::async_payments::AsyncPaymentsMessage
461#[derive(Clone, Debug)]
462pub enum AsyncPaymentsContext {
463	/// Context used by a [`BlindedMessagePath`] provided out-of-band to an async recipient, where the
464	/// context is provided back to the static invoice server in corresponding [`OfferPathsRequest`]s.
465	///
466	/// [`OfferPathsRequest`]: crate::onion_message::async_payments::OfferPathsRequest
467	OfferPathsRequest {
468		/// An identifier for the async recipient that is requesting blinded paths to include in their
469		/// [`Offer::paths`]. This ID will be surfaced when the async recipient eventually sends a
470		/// corresponding [`ServeStaticInvoice`] message, and can be used to rate limit the recipient.
471		///
472		/// [`Offer::paths`]: crate::offers::offer::Offer::paths
473		/// [`ServeStaticInvoice`]: crate::onion_message::async_payments::ServeStaticInvoice
474		recipient_id: Vec<u8>,
475		/// An optional field indicating the time as duration since the Unix epoch at which this path
476		/// expires and messages sent over it should be ignored.
477		///
478		/// Useful to timeout async recipients that are no longer supported as clients.
479		path_absolute_expiry: Option<core::time::Duration>,
480	},
481	/// Context used by a reply path to an [`OfferPathsRequest`], provided back to us as an async
482	/// recipient in corresponding [`OfferPaths`] messages from the static invoice server.
483	///
484	/// [`OfferPathsRequest`]: crate::onion_message::async_payments::OfferPathsRequest
485	/// [`OfferPaths`]: crate::onion_message::async_payments::OfferPaths
486	OfferPaths {
487		/// The "slot" in the static invoice server's database that the invoice corresponding to these
488		/// offer paths should go into, originally set by us in [`OfferPathsRequest::invoice_slot`]. This
489		/// value allows us as the recipient to replace a specific invoice that is stored by the server,
490		/// which is useful for limiting the number of invoices stored by the server while also keeping
491		/// all the invoices persisted with the server fresh.
492		///
493		/// [`OfferPathsRequest::invoice_slot`]: crate::onion_message::async_payments::OfferPathsRequest::invoice_slot
494		invoice_slot: u16,
495		/// The time as duration since the Unix epoch at which this path expires and messages sent over
496		/// it should be ignored.
497		///
498		/// This avoids the situation where the [`OfferPaths`] message is very delayed and thus
499		/// outdated.
500		///
501		/// [`OfferPaths`]: crate::onion_message::async_payments::OfferPaths
502		path_absolute_expiry: core::time::Duration,
503	},
504	/// Context used by a reply path to an [`OfferPaths`] message, provided back to us as the static
505	/// invoice server in corresponding [`ServeStaticInvoice`] messages.
506	///
507	/// [`OfferPaths`]: crate::onion_message::async_payments::OfferPaths
508	/// [`ServeStaticInvoice`]: crate::onion_message::async_payments::ServeStaticInvoice
509	ServeStaticInvoice {
510		/// An identifier for the async recipient that is requesting that a [`StaticInvoice`] be served
511		/// on their behalf.
512		///
513		/// Useful when surfaced alongside the below `invoice_slot` when payers send an
514		/// [`InvoiceRequest`], to pull the specific static invoice from the database.
515		///
516		/// Also useful to rate limit the invoices being persisted on behalf of a particular recipient.
517		///
518		/// This id will be provided back to us as the static invoice server via
519		/// [`OffersContext::StaticInvoiceRequested::recipient_id`].
520		///
521		/// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice
522		/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
523		recipient_id: Vec<u8>,
524		/// The slot number for the specific [`StaticInvoice`] that the recipient is requesting be
525		/// served on their behalf. Useful when surfaced alongside the above `recipient_id` when payers
526		/// send an [`InvoiceRequest`], to pull the specific static invoice from the database. This id
527		/// will be provided back to us as the static invoice server via
528		/// [`OffersContext::StaticInvoiceRequested::invoice_slot`].
529		///
530		/// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice
531		/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
532		invoice_slot: u16,
533		/// The time as duration since the Unix epoch at which this path expires and messages sent over
534		/// it should be ignored.
535		///
536		/// Useful to timeout async recipients that are no longer supported as clients.
537		path_absolute_expiry: core::time::Duration,
538	},
539	/// Context used by a reply path to a [`ServeStaticInvoice`] message, provided back to us in
540	/// corresponding [`StaticInvoicePersisted`] messages.
541	///
542	/// [`ServeStaticInvoice`]: crate::onion_message::async_payments::ServeStaticInvoice
543	/// [`StaticInvoicePersisted`]: crate::onion_message::async_payments::StaticInvoicePersisted
544	StaticInvoicePersisted {
545		/// The id of the offer in the cache corresponding to the [`StaticInvoice`] that has been
546		/// persisted. This invoice is now ready to be provided by the static invoice server in response
547		/// to [`InvoiceRequest`]s, so the corresponding offer can be marked as ready to receive
548		/// payments.
549		///
550		/// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice
551		/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
552		offer_id: OfferId,
553		/// The time as duration since the Unix epoch at which the invoice corresponding to this path
554		/// was created. Useful to know when an invoice needs replacement.
555		invoice_created_at: core::time::Duration,
556	},
557	/// Context contained within the reply [`BlindedMessagePath`] we put in outbound
558	/// [`HeldHtlcAvailable`] messages, provided back to us in corresponding [`ReleaseHeldHtlc`]
559	/// messages if we are an always-online sender paying an async recipient.
560	///
561	/// [`HeldHtlcAvailable`]: crate::onion_message::async_payments::HeldHtlcAvailable
562	/// [`ReleaseHeldHtlc`]: crate::onion_message::async_payments::ReleaseHeldHtlc
563	OutboundPayment {
564		/// ID used when payment to the originating [`Offer`] was initiated. Useful for us to identify
565		/// which of our pending outbound payments should be released to its often-offline payee.
566		///
567		/// [`Offer`]: crate::offers::offer::Offer
568		payment_id: PaymentId,
569	},
570	/// Context contained within the [`BlindedMessagePath`]s we put in static invoices, provided back
571	/// to us in corresponding [`HeldHtlcAvailable`] messages.
572	///
573	/// [`HeldHtlcAvailable`]: crate::onion_message::async_payments::HeldHtlcAvailable
574	InboundPayment {
575		/// The time as duration since the Unix epoch at which this path expires and messages sent over
576		/// it should be ignored. Without this, anyone with the path corresponding to this context is
577		/// able to trivially ask if we're online forever.
578		path_absolute_expiry: core::time::Duration,
579	},
580	/// Context contained within the reply [`BlindedMessagePath`] put in outbound
581	/// [`HeldHtlcAvailable`] messages, provided back to the async sender's always-online counterparty
582	/// in corresponding [`ReleaseHeldHtlc`] messages.
583	///
584	/// [`HeldHtlcAvailable`]: crate::onion_message::async_payments::HeldHtlcAvailable
585	/// [`ReleaseHeldHtlc`]: crate::onion_message::async_payments::ReleaseHeldHtlc
586	ReleaseHeldHtlc {
587		/// An identifier for the HTLC that should be released by us as the sender's always-online
588		/// channel counterparty to the often-offline recipient.
589		intercept_id: InterceptId,
590		/// The short channel id alias corresponding to the to-be-released inbound HTLC, to help locate
591		/// the HTLC internally if the [`ReleaseHeldHtlc`] races our node decoding the held HTLC's
592		/// onion.
593		///
594		/// We use the outbound scid alias because it is stable even if the channel splices, unlike
595		/// regular short channel ids.
596		///
597		/// [`ReleaseHeldHtlc`]: crate::onion_message::async_payments::ReleaseHeldHtlc
598		prev_outbound_scid_alias: u64,
599		/// The id of the to-be-released HTLC, to help locate the HTLC internally if the
600		/// [`ReleaseHeldHtlc`] races our node decoding the held HTLC's onion.
601		///
602		/// [`ReleaseHeldHtlc`]: crate::onion_message::async_payments::ReleaseHeldHtlc
603		htlc_id: u64,
604	},
605}
606
607impl_writeable_tlv_based_enum!(MessageContext,
608	{0, Offers} => (),
609	{1, Custom} => (),
610	{2, AsyncPayments} => (),
611	{3, DNSResolver} => (),
612);
613
614// Note: Several TLV fields (`nonce`, `hmac`, etc.) were removed in LDK v0.2 following the
615// introduction of `ReceiveAuthKey`-based authentication for inbound `BlindedMessagePath`s. Because
616// we do not support receiving to those contexts anymore (they will fail the `ReceiveAuthKey`-based
617// authentication checks), we can reuse those fields here.
618impl_writeable_tlv_based_enum!(OffersContext,
619	(0, InvoiceRequest) => {
620		(0, nonce, required),
621	},
622	(1, OutboundPayment) => {
623		(0, payment_id, required),
624		(1, nonce, required),
625	},
626	(2, InboundPayment) => {
627		(0, payment_hash, required),
628	},
629	(3, StaticInvoiceRequested) => {
630		(0, recipient_id, required),
631		(2, invoice_slot, required),
632		(4, path_absolute_expiry, required),
633	},
634);
635
636impl_writeable_tlv_based_enum!(AsyncPaymentsContext,
637	(0, OutboundPayment) => {
638		(0, payment_id, required),
639	},
640	(1, InboundPayment) => {
641		(4, path_absolute_expiry, required),
642	},
643	(2, OfferPaths) => {
644		(0, path_absolute_expiry, required),
645		(2, invoice_slot, required),
646	},
647	(3, StaticInvoicePersisted) => {
648		(0, offer_id, required),
649		(2, invoice_created_at, required),
650	},
651	(4, OfferPathsRequest) => {
652		(0, recipient_id, required),
653		(2, path_absolute_expiry, option),
654	},
655	(5, ServeStaticInvoice) => {
656		(0, recipient_id, required),
657		(2, invoice_slot, required),
658		(4, path_absolute_expiry, required),
659	},
660	(6, ReleaseHeldHtlc) => {
661		(0, intercept_id, required),
662		(2, prev_outbound_scid_alias, required),
663		(4, htlc_id, required),
664	},
665);
666
667/// Contains a simple nonce for use in a blinded path's context.
668///
669/// Such a context is required when receiving a [`DNSSECProof`] message.
670///
671/// [`DNSSECProof`]: crate::onion_message::dns_resolution::DNSSECProof
672#[derive(Clone, Debug, Hash, PartialEq, Eq)]
673pub struct DNSResolverContext {
674	/// A nonce which uniquely describes a DNS resolution, useful for looking up metadata about the
675	/// request.
676	pub nonce: [u8; 16],
677}
678
679impl_writeable_tlv_based!(DNSResolverContext, {
680	(0, nonce, required),
681});
682
683/// Represents the padding round off size (in bytes) that is used
684/// to pad message blinded path's [`BlindedHop`]
685pub(crate) const MESSAGE_PADDING_ROUND_OFF: usize = 100;
686
687/// The maximum number of dummy hops that can be added to a blinded path.
688/// This is to prevent paths from becoming too long and potentially causing
689/// issues with message processing or routing.
690pub const MAX_DUMMY_HOPS_COUNT: usize = 10;
691
692/// Construct blinded onion message hops for the given `intermediate_nodes` and `recipient_node_id`.
693pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
694	secp_ctx: &Secp256k1<T>, intermediate_nodes: &[MessageForwardNode],
695	recipient_node_id: PublicKey, dummy_hop_count: usize, context: MessageContext,
696	session_priv: &SecretKey, local_node_receive_key: ReceiveAuthKey,
697) -> Vec<BlindedHop> {
698	let dummy_count = cmp::min(dummy_hop_count, MAX_DUMMY_HOPS_COUNT);
699	let pks = intermediate_nodes
700		.iter()
701		.map(|node| (node.node_id, None))
702		.chain(
703			core::iter::repeat((recipient_node_id, Some(local_node_receive_key))).take(dummy_count),
704		)
705		.chain(core::iter::once((recipient_node_id, Some(local_node_receive_key))));
706	let is_compact = intermediate_nodes.iter().any(|node| node.short_channel_id.is_some());
707
708	let tlvs = pks
709		.clone()
710		.skip(1) // The first node's TLVs contains the next node's pubkey
711		.zip(intermediate_nodes.iter().map(|node| node.short_channel_id))
712		.map(|((pubkey, _), scid)| match scid {
713			Some(scid) => NextMessageHop::ShortChannelId(scid),
714			None => NextMessageHop::NodeId(pubkey),
715		})
716		.map(|next_hop| {
717			ControlTlvs::Forward(ForwardTlvs { next_hop, next_blinding_override: None })
718		})
719		.chain((0..dummy_count).map(|_| ControlTlvs::Dummy))
720		.chain(core::iter::once(ControlTlvs::Receive(ReceiveTlvs { context: Some(context) })));
721
722	if is_compact {
723		let path = pks.zip(tlvs);
724		utils::construct_blinded_hops(secp_ctx, path, session_priv)
725	} else {
726		let path =
727			pks.zip(tlvs.map(|tlv| BlindedPathWithPadding {
728				tlvs: tlv,
729				round_off: MESSAGE_PADDING_ROUND_OFF,
730			}));
731		utils::construct_blinded_hops(secp_ctx, path, session_priv)
732	}
733}