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 bitcoin::hashes::hmac::Hmac;
18use bitcoin::hashes::sha256::Hash as Sha256;
19use crate::blinded_path::{BlindedHop, BlindedPath, Direction, IntroductionNode, NodeIdLookUp};
20use crate::blinded_path::utils;
21use crate::io;
22use crate::io::Cursor;
23use crate::ln::channelmanager::PaymentId;
24use crate::ln::msgs::DecodeError;
25use crate::ln::onion_utils;
26use crate::types::payment::PaymentHash;
27use crate::offers::nonce::Nonce;
28use crate::onion_message::packet::ControlTlvs;
29use crate::routing::gossip::{NodeId, ReadOnlyNetworkGraph};
30use crate::sign::{EntropySource, NodeSigner, Recipient};
31use crate::crypto::streams::ChaChaPolyReadAdapter;
32use crate::util::scid_utils;
33use crate::util::ser::{FixedLengthReader, LengthReadableArgs, Readable, Writeable, Writer};
34
35use core::mem;
36use core::ops::Deref;
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, context: MessageContext, entropy_source: ES, secp_ctx: &Secp256k1<T>
59	) -> Result<Self, ()> where ES::Target: EntropySource {
60		Self::new(&[], recipient_node_id, context, entropy_source, secp_ctx)
61	}
62
63	/// Create a path for an onion message, to be forwarded along `node_pks`. The last node
64	/// pubkey in `node_pks` will be the destination node.
65	///
66	/// Errors if no hops are provided or if `node_pk`(s) are invalid.
67	//  TODO: make all payloads the same size with padding + add dummy hops
68	pub fn new<ES: Deref, T: secp256k1::Signing + secp256k1::Verification>(
69		intermediate_nodes: &[MessageForwardNode], recipient_node_id: PublicKey,
70		context: MessageContext, entropy_source: ES, secp_ctx: &Secp256k1<T>,
71	) -> Result<Self, ()> where ES::Target: EntropySource {
72		let introduction_node = IntroductionNode::NodeId(
73			intermediate_nodes.first().map_or(recipient_node_id, |n| n.node_id)
74		);
75		let blinding_secret_bytes = entropy_source.get_secure_random_bytes();
76		let blinding_secret = SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted");
77
78		Ok(Self(BlindedPath {
79			introduction_node,
80			blinding_point: PublicKey::from_secret_key(secp_ctx, &blinding_secret),
81			blinded_hops: blinded_hops(
82				secp_ctx, intermediate_nodes, recipient_node_id,
83				context, &blinding_secret,
84			).map_err(|_| ())?,
85		}))
86	}
87
88	/// Attempts to a use a compact representation for the [`IntroductionNode`] by using a directed
89	/// short channel id from a channel in `network_graph` leading to the introduction node.
90	///
91	/// While this may result in a smaller encoding, there is a trade off in that the path may
92	/// become invalid if the channel is closed or hasn't been propagated via gossip. Therefore,
93	/// calling this may not be suitable for long-lived blinded paths.
94	pub fn use_compact_introduction_node(&mut self, network_graph: &ReadOnlyNetworkGraph) {
95		if let IntroductionNode::NodeId(pubkey) = &self.0.introduction_node {
96			let node_id = NodeId::from_pubkey(pubkey);
97			if let Some(node_info) = network_graph.node(&node_id) {
98				if let Some((scid, channel_info)) = node_info
99					.channels
100						.iter()
101						.filter_map(|scid| network_graph.channel(*scid).map(|info| (*scid, info)))
102						.min_by_key(|(scid, _)| scid_utils::block_from_scid(*scid))
103				{
104					let direction = if node_id == channel_info.node_one {
105						Direction::NodeOne
106					} else {
107						debug_assert_eq!(node_id, channel_info.node_two);
108						Direction::NodeTwo
109					};
110					self.0.introduction_node =
111						IntroductionNode::DirectedShortChannelId(direction, scid);
112				}
113			}
114		}
115	}
116
117	/// Returns the introduction [`NodeId`] of the blinded path, if it is publicly reachable (i.e.,
118	/// it is found in the network graph).
119	pub fn public_introduction_node_id<'a>(
120		&self, network_graph: &'a ReadOnlyNetworkGraph
121	) -> Option<&'a NodeId> {
122		self.0.public_introduction_node_id(network_graph)
123	}
124
125	/// The [`IntroductionNode`] of the blinded path.
126	pub fn introduction_node(&self) -> &IntroductionNode {
127		&self.0.introduction_node
128	}
129
130	/// Used by the [`IntroductionNode`] to decrypt its [`encrypted_payload`] to forward the message.
131	///
132	/// [`encrypted_payload`]: BlindedHop::encrypted_payload
133	pub fn blinding_point(&self) -> PublicKey {
134		self.0.blinding_point
135	}
136
137	/// The [`BlindedHop`]s within the blinded path.
138	pub fn blinded_hops(&self) -> &[BlindedHop] {
139		&self.0.blinded_hops
140	}
141
142	/// Advance the blinded onion message path by one hop, making the second hop into the new
143	/// introduction node.
144	///
145	/// Will only modify `self` when returning `Ok`.
146	pub fn advance_path_by_one<NS: Deref, NL: Deref, T>(
147		&mut self, node_signer: &NS, node_id_lookup: &NL, secp_ctx: &Secp256k1<T>
148	) -> Result<(), ()>
149	where
150		NS::Target: NodeSigner,
151		NL::Target: NodeIdLookUp,
152		T: secp256k1::Signing + secp256k1::Verification,
153	{
154		let control_tlvs_ss = node_signer.ecdh(Recipient::Node, &self.0.blinding_point, None)?;
155		let rho = onion_utils::gen_rho_from_shared_secret(&control_tlvs_ss.secret_bytes());
156		let encrypted_control_tlvs = &self.0.blinded_hops.get(0).ok_or(())?.encrypted_payload;
157		let mut s = Cursor::new(encrypted_control_tlvs);
158		let mut reader = FixedLengthReader::new(&mut s, encrypted_control_tlvs.len() as u64);
159		match ChaChaPolyReadAdapter::read(&mut reader, rho) {
160			Ok(ChaChaPolyReadAdapter {
161				readable: ControlTlvs::Forward(ForwardTlvs { next_hop, next_blinding_override })
162			}) => {
163				let next_node_id = match next_hop {
164					NextMessageHop::NodeId(pubkey) => pubkey,
165					NextMessageHop::ShortChannelId(scid) => match node_id_lookup.next_node_id(scid) {
166						Some(pubkey) => pubkey,
167						None => return Err(()),
168					},
169				};
170				let mut new_blinding_point = match next_blinding_override {
171					Some(blinding_point) => blinding_point,
172					None => {
173						onion_utils::next_hop_pubkey(secp_ctx, self.0.blinding_point,
174							control_tlvs_ss.as_ref()).map_err(|_| ())?
175					}
176				};
177				mem::swap(&mut self.0.blinding_point, &mut new_blinding_point);
178				self.0.introduction_node = IntroductionNode::NodeId(next_node_id);
179				self.0.blinded_hops.remove(0);
180				Ok(())
181			},
182			_ => Err(())
183		}
184	}
185
186	pub(crate) fn introduction_node_mut(&mut self) -> &mut IntroductionNode {
187		&mut self.0.introduction_node
188	}
189
190	#[cfg(test)]
191	pub fn from_raw(
192		introduction_node_id: PublicKey, blinding_point: PublicKey, blinded_hops: Vec<BlindedHop>
193	) -> Self {
194		Self(BlindedPath {
195			introduction_node: IntroductionNode::NodeId(introduction_node_id),
196			blinding_point,
197			blinded_hops,
198		})
199	}
200
201	#[cfg(test)]
202	pub fn clear_blinded_hops(&mut self) {
203		self.0.blinded_hops.clear()
204	}
205}
206
207/// The next hop to forward an onion message along its path.
208///
209/// Note that payment blinded paths always specify their next hop using an explicit node id.
210#[derive(Clone, Debug, Hash, PartialEq, Eq)]
211pub enum NextMessageHop {
212	/// The node id of the next hop.
213	NodeId(PublicKey),
214	/// The short channel id leading to the next hop.
215	ShortChannelId(u64),
216}
217
218/// An intermediate node, and possibly a short channel id leading to the next node.
219#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
220pub struct MessageForwardNode {
221	/// This node's pubkey.
222	pub node_id: PublicKey,
223	/// The channel between `node_id` and the next hop. If set, the constructed [`BlindedHop`]'s
224	/// `encrypted_payload` will use this instead of the next [`MessageForwardNode::node_id`] for a
225	/// more compact representation.
226	pub short_channel_id: Option<u64>,
227}
228
229/// TLVs to encode in an intermediate onion message packet's hop data. When provided in a blinded
230/// route, they are encoded into [`BlindedHop::encrypted_payload`].
231pub(crate) struct ForwardTlvs {
232	/// The next hop in the onion message's path.
233	pub(crate) next_hop: NextMessageHop,
234	/// Senders to a blinded path use this value to concatenate the route they find to the
235	/// introduction node with the blinded path.
236	pub(crate) next_blinding_override: Option<PublicKey>,
237}
238
239/// Similar to [`ForwardTlvs`], but these TLVs are for the final node.
240pub(crate) struct ReceiveTlvs {
241	/// If `context` is `Some`, it is used to identify the blinded path that this onion message is
242	/// sending to. This is useful for receivers to check that said blinded path is being used in
243	/// the right context.
244	pub context: Option<MessageContext>
245}
246
247impl Writeable for ForwardTlvs {
248	fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
249		let (next_node_id, short_channel_id) = match self.next_hop {
250			NextMessageHop::NodeId(pubkey) => (Some(pubkey), None),
251			NextMessageHop::ShortChannelId(scid) => (None, Some(scid)),
252		};
253		// TODO: write padding
254		encode_tlv_stream!(writer, {
255			(2, short_channel_id, option),
256			(4, next_node_id, option),
257			(8, self.next_blinding_override, option)
258		});
259		Ok(())
260	}
261}
262
263impl Writeable for ReceiveTlvs {
264	fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
265		// TODO: write padding
266		encode_tlv_stream!(writer, {
267			(65537, self.context, option),
268		});
269		Ok(())
270	}
271}
272
273/// Additional data included by the recipient in a [`BlindedMessagePath`].
274///
275/// This data is encrypted by the recipient and will be given to the corresponding message handler
276/// when handling a message sent over the [`BlindedMessagePath`]. The recipient can use this data to
277/// authenticate the message or for further processing if needed.
278#[derive(Clone, Debug)]
279pub enum MessageContext {
280	/// Context specific to an [`OffersMessage`].
281	///
282	/// [`OffersMessage`]: crate::onion_message::offers::OffersMessage
283	Offers(OffersContext),
284	/// Context specific to an [`AsyncPaymentsMessage`].
285	///
286	/// [`AsyncPaymentsMessage`]: crate::onion_message::async_payments::AsyncPaymentsMessage
287	AsyncPayments(AsyncPaymentsContext),
288	/// Represents a context for a blinded path used in a reply path when requesting a DNSSEC proof
289	/// in a [`DNSResolverMessage`].
290	///
291	/// [`DNSResolverMessage`]: crate::onion_message::dns_resolution::DNSResolverMessage
292	DNSResolver(DNSResolverContext),
293	/// Context specific to a [`CustomOnionMessageHandler::CustomMessage`].
294	///
295	/// [`CustomOnionMessageHandler::CustomMessage`]: crate::onion_message::messenger::CustomOnionMessageHandler::CustomMessage
296	Custom(Vec<u8>),
297}
298
299/// Contains data specific to an [`OffersMessage`].
300///
301/// [`OffersMessage`]: crate::onion_message::offers::OffersMessage
302#[derive(Clone, Debug, Eq, PartialEq)]
303pub enum OffersContext {
304	/// Context used by a [`BlindedMessagePath`] within an [`Offer`].
305	///
306	/// This variant is intended to be received when handling an [`InvoiceRequest`].
307	///
308	/// [`Offer`]: crate::offers::offer::Offer
309	/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
310	InvoiceRequest {
311		/// A nonce used for authenticating that an [`InvoiceRequest`] is for a valid [`Offer`] and
312		/// for deriving the offer's signing keys.
313		///
314		/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
315		/// [`Offer`]: crate::offers::offer::Offer
316		nonce: Nonce,
317	},
318	/// Context used by a [`BlindedMessagePath`] within a [`Refund`] or as a reply path for an
319	/// [`InvoiceRequest`].
320	///
321	/// This variant is intended to be received when handling a [`Bolt12Invoice`] or an
322	/// [`InvoiceError`].
323	///
324	/// [`Refund`]: crate::offers::refund::Refund
325	/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
326	/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
327	/// [`InvoiceError`]: crate::offers::invoice_error::InvoiceError
328	OutboundPayment {
329		/// Payment ID used when creating a [`Refund`] or [`InvoiceRequest`].
330		///
331		/// [`Refund`]: crate::offers::refund::Refund
332		/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
333		payment_id: PaymentId,
334
335		/// A nonce used for authenticating that a [`Bolt12Invoice`] is for a valid [`Refund`] or
336		/// [`InvoiceRequest`] and for deriving their signing keys.
337		///
338		/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
339		/// [`Refund`]: crate::offers::refund::Refund
340		/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
341		nonce: Nonce,
342
343		/// Authentication code for the [`PaymentId`], which should be checked when the context is
344		/// used with an [`InvoiceError`].
345		///
346		/// [`InvoiceError`]: crate::offers::invoice_error::InvoiceError
347		hmac: Option<Hmac<Sha256>>,
348	},
349	/// Context used by a [`BlindedMessagePath`] as a reply path for a [`Bolt12Invoice`].
350	///
351	/// This variant is intended to be received when handling an [`InvoiceError`].
352	///
353	/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
354	/// [`InvoiceError`]: crate::offers::invoice_error::InvoiceError
355	InboundPayment {
356		/// The same payment hash as [`Bolt12Invoice::payment_hash`].
357		///
358		/// [`Bolt12Invoice::payment_hash`]: crate::offers::invoice::Bolt12Invoice::payment_hash
359		payment_hash: PaymentHash,
360
361		/// A nonce used for authenticating that a received [`InvoiceError`] is for a valid
362		/// sent [`Bolt12Invoice`].
363		///
364		/// [`InvoiceError`]: crate::offers::invoice_error::InvoiceError
365		/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
366		nonce: Nonce,
367
368		/// Authentication code for the [`PaymentHash`], which should be checked when the context is
369		/// used to log the received [`InvoiceError`].
370		///
371		/// [`InvoiceError`]: crate::offers::invoice_error::InvoiceError
372		hmac: Hmac<Sha256>,
373	},
374}
375
376/// Contains data specific to an [`AsyncPaymentsMessage`].
377///
378/// [`AsyncPaymentsMessage`]: crate::onion_message::async_payments::AsyncPaymentsMessage
379#[derive(Clone, Debug)]
380pub enum AsyncPaymentsContext {
381	/// Context contained within the reply [`BlindedMessagePath`] we put in outbound
382	/// [`HeldHtlcAvailable`] messages, provided back to us in corresponding [`ReleaseHeldHtlc`]
383	/// messages.
384	///
385	/// [`HeldHtlcAvailable`]: crate::onion_message::async_payments::HeldHtlcAvailable
386	/// [`ReleaseHeldHtlc`]: crate::onion_message::async_payments::ReleaseHeldHtlc
387	OutboundPayment {
388		/// ID used when payment to the originating [`Offer`] was initiated. Useful for us to identify
389		/// which of our pending outbound payments should be released to its often-offline payee.
390		///
391		/// [`Offer`]: crate::offers::offer::Offer
392		payment_id: PaymentId,
393		/// A nonce used for authenticating that a [`ReleaseHeldHtlc`] message is valid for a preceding
394		/// [`HeldHtlcAvailable`] message.
395		///
396		/// [`ReleaseHeldHtlc`]: crate::onion_message::async_payments::ReleaseHeldHtlc
397		/// [`HeldHtlcAvailable`]: crate::onion_message::async_payments::HeldHtlcAvailable
398		nonce: Nonce,
399		/// Authentication code for the [`PaymentId`].
400		///
401		/// Prevents the recipient from being able to deanonymize us by creating a blinded path to us
402		/// containing the expected [`PaymentId`].
403		hmac: Hmac<Sha256>,
404	},
405}
406
407impl_writeable_tlv_based_enum!(MessageContext,
408	{0, Offers} => (),
409	{1, Custom} => (),
410	{2, AsyncPayments} => (),
411	{3, DNSResolver} => (),
412);
413
414impl_writeable_tlv_based_enum!(OffersContext,
415	(0, InvoiceRequest) => {
416		(0, nonce, required),
417	},
418	(1, OutboundPayment) => {
419		(0, payment_id, required),
420		(1, nonce, required),
421		(2, hmac, option),
422	},
423	(2, InboundPayment) => {
424		(0, payment_hash, required),
425		(1, nonce, required),
426		(2, hmac, required)
427	},
428);
429
430impl_writeable_tlv_based_enum!(AsyncPaymentsContext,
431	(0, OutboundPayment) => {
432		(0, payment_id, required),
433		(2, nonce, required),
434		(4, hmac, required),
435	},
436);
437
438/// Contains a simple nonce for use in a blinded path's context.
439///
440/// Such a context is required when receiving a [`DNSSECProof`] message.
441///
442/// [`DNSSECProof`]: crate::onion_message::dns_resolution::DNSSECProof
443#[derive(Clone, Debug, Hash, PartialEq, Eq)]
444pub struct DNSResolverContext {
445	/// A nonce which uniquely describes a DNS resolution.
446	///
447	/// When we receive a DNSSEC proof message, we should check that it was sent over the blinded
448	/// path we included in the request by comparing a stored nonce with this one.
449	pub nonce: [u8; 16],
450}
451
452impl_writeable_tlv_based!(DNSResolverContext, {
453	(0, nonce, required),
454});
455
456/// Construct blinded onion message hops for the given `intermediate_nodes` and `recipient_node_id`.
457pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
458	secp_ctx: &Secp256k1<T>, intermediate_nodes: &[MessageForwardNode],
459	recipient_node_id: PublicKey, context: MessageContext, session_priv: &SecretKey,
460) -> Result<Vec<BlindedHop>, secp256k1::Error> {
461	let pks = intermediate_nodes.iter().map(|node| node.node_id)
462		.chain(core::iter::once(recipient_node_id));
463	let tlvs = pks.clone()
464		.skip(1) // The first node's TLVs contains the next node's pubkey
465		.zip(intermediate_nodes.iter().map(|node| node.short_channel_id))
466		.map(|(pubkey, scid)| match scid {
467			Some(scid) => NextMessageHop::ShortChannelId(scid),
468			None => NextMessageHop::NodeId(pubkey),
469		})
470		.map(|next_hop| ControlTlvs::Forward(ForwardTlvs { next_hop, next_blinding_override: None }))
471		.chain(core::iter::once(ControlTlvs::Receive(ReceiveTlvs{ context: Some(context) })));
472
473	let path = pks.zip(tlvs);
474
475	utils::construct_blinded_hops(secp_ctx, path, session_priv)
476}
477