bitcoin_payment_instructions/
onion_message_resolver.rs

1//! A [`HrnResolver`] which uses lightning onion messages and DNSSEC proofs to request DNS
2//! resolution directly from untrusted lightning nodes, providing privacy through onion routing.
3
4use std::boxed::Box;
5use std::collections::HashMap;
6use std::future::Future;
7use std::ops::Deref;
8use std::pin::Pin;
9use std::sync::atomic::{AtomicUsize, Ordering};
10use std::sync::{Arc, Mutex, RwLock};
11use std::task::{Context, Poll, Waker};
12use std::vec::Vec;
13
14use lightning::blinded_path::message::{DNSResolverContext, MessageContext};
15use lightning::ln::channelmanager::PaymentId;
16use lightning::onion_message::dns_resolution::{
17	DNSResolverMessage, DNSResolverMessageHandler, DNSSECProof, DNSSECQuery, OMNameResolver,
18};
19use lightning::onion_message::messenger::{
20	Destination, MessageSendInstructions, Responder, ResponseInstruction,
21};
22use lightning::routing::gossip::NetworkGraph;
23use lightning::sign::EntropySource;
24use lightning::util::logger::Logger;
25
26use crate::hrn_resolution::{
27	HrnResolution, HrnResolutionFuture, HrnResolver, HumanReadableName, LNURLResolutionFuture,
28};
29use crate::Amount;
30
31struct OsRng;
32impl EntropySource for OsRng {
33	fn get_secure_random_bytes(&self) -> [u8; 32] {
34		let mut res = [0; 32];
35		getrandom::fill(&mut res).expect("Fetching system randomness should always succeed");
36		res
37	}
38}
39
40struct ChannelState {
41	waker: Option<Waker>,
42	result: Option<HrnResolution>,
43}
44
45struct ChannelSend(Arc<Mutex<ChannelState>>);
46
47impl ChannelSend {
48	fn complete(self, result: HrnResolution) {
49		let mut state = self.0.lock().unwrap();
50		state.result = Some(result);
51		if let Some(waker) = state.waker.take() {
52			waker.wake();
53		}
54	}
55
56	fn receiver_alive(&self) -> bool {
57		Arc::strong_count(&self.0) > 1
58	}
59}
60
61struct ChannelRecv(Arc<Mutex<ChannelState>>);
62
63impl Future for ChannelRecv {
64	type Output = HrnResolution;
65	fn poll(self: Pin<&mut Self>, context: &mut Context) -> Poll<HrnResolution> {
66		let mut state = self.0.lock().unwrap();
67		if let Some(res) = state.result.take() {
68			debug_assert!(state.waker.is_none());
69			Poll::Ready(res)
70		} else {
71			state.waker = Some(context.waker().clone());
72			Poll::Pending
73		}
74	}
75}
76
77fn channel() -> (ChannelSend, ChannelRecv) {
78	let state = Arc::new(Mutex::new(ChannelState { waker: None, result: None }));
79	(ChannelSend(Arc::clone(&state)), ChannelRecv(state))
80}
81
82/// A [`HrnResolver`] which uses lightning onion messages and DNSSEC proofs to request DNS
83/// resolution directly from untrusted lightning nodes, providing privacy through onion routing.
84///
85/// This implements LDK's [`DNSResolverMessageHandler`], which it uses to send onion messages and
86/// process response messages.
87///
88/// Note that because this implementation does not assume an async runtime, queries which are not
89/// responded to *may hang forever*. You must always wrap resolution futures to ensure they time
90/// out properly, eg via `tokio::time::timeout`.
91///
92/// Note that after a query begines, [`PeerManager::process_events`] should be called to ensure the
93/// query message goes out in a timely manner. You can call [`Self::register_post_queue_action`] to
94/// have this happen automatically.
95///
96/// [`PeerManager::process_events`]: lightning::ln::peer_handler::PeerManager::process_events
97pub struct LDKOnionMessageDNSSECHrnResolver<N: Deref<Target = NetworkGraph<L>>, L: Deref>
98where
99	L::Target: Logger,
100{
101	network_graph: N,
102	resolver: OMNameResolver,
103	next_id: AtomicUsize,
104	pending_resolutions: Mutex<HashMap<HumanReadableName, Vec<(PaymentId, ChannelSend)>>>,
105	message_queue: Mutex<Vec<(DNSResolverMessage, MessageSendInstructions)>>,
106	pm_event_poker: RwLock<Option<Box<dyn Fn() + Send + Sync>>>,
107}
108
109impl<N: Deref<Target = NetworkGraph<L>>, L: Deref> LDKOnionMessageDNSSECHrnResolver<N, L>
110where
111	L::Target: Logger,
112{
113	/// Constructs a new [`LDKOnionMessageDNSSECHrnResolver`].
114	///
115	/// See the struct-level documentation for more info.
116	pub fn new(network_graph: N) -> Self {
117		Self {
118			network_graph,
119			next_id: AtomicUsize::new(0),
120			// TODO: Swap for `new_without_expiry_validation` when we upgrade to LDK 0.2
121			resolver: OMNameResolver::new(0, 0),
122			pending_resolutions: Mutex::new(HashMap::new()),
123			message_queue: Mutex::new(Vec::new()),
124			pm_event_poker: RwLock::new(None),
125		}
126	}
127
128	/// Sets a callback which is called any time a new resolution begins and a message is available
129	/// to be sent. This should generally call [`PeerManager::process_events`].
130	///
131	/// [`PeerManager::process_events`]: lightning::ln::peer_handler::PeerManager::process_events
132	pub fn register_post_queue_action(&self, callback: Box<dyn Fn() + Send + Sync>) {
133		*self.pm_event_poker.write().unwrap() = Some(callback);
134	}
135
136	fn init_resolve_hrn<'a>(
137		&'a self, hrn: &HumanReadableName,
138	) -> Result<ChannelRecv, &'static str> {
139		#[cfg(feature = "std")]
140		{
141			use std::time::SystemTime;
142			let clock_err =
143				"DNSSEC validation relies on having a correct system clock. It is currently set before 1970.";
144			let now =
145				SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).map_err(|_| clock_err)?;
146			// Use `now / 60` as the block height to expire pending requests after 1-2 minutes.
147			self.resolver.new_best_block((now.as_secs() / 60) as u32, now.as_secs() as u32);
148		}
149
150		let mut dns_resolvers = Vec::new();
151		for (node_id, node) in self.network_graph.read_only().nodes().unordered_iter() {
152			if let Some(info) = &node.announcement_info {
153				// Sadly, 31 nodes currently squat on the DNS Resolver feature bit
154				// without speaking it.
155				// Its unclear why they're doing so, but none of them currently
156				// also have the onion messaging feature bit set, so here we check
157				// for both.
158				let supports_dns = info.features().supports_dns_resolution();
159				let supports_om = info.features().supports_onion_messages();
160				if supports_dns && supports_om {
161					if let Ok(pubkey) = node_id.as_pubkey() {
162						dns_resolvers.push(Destination::Node(pubkey));
163					}
164				}
165			}
166			if dns_resolvers.len() > 5 {
167				break;
168			}
169		}
170		if dns_resolvers.is_empty() {
171			return Err(
172				"Failed to find any DNS resolving nodes, check your network graph is synced",
173			);
174		}
175
176		let counter = self.next_id.fetch_add(1, Ordering::Relaxed) as u64;
177		let mut payment_id = [0; 32];
178		payment_id[..8].copy_from_slice(&counter.to_ne_bytes());
179		let payment_id = PaymentId(payment_id);
180
181		let err = "The provided HRN did not fit in a DNS request";
182		// TODO: Once LDK 0.2 ships with a new context authentication method, we shouldn't need the
183		// RNG here and can stop depending on std.
184		let (query, dns_context) =
185			self.resolver.resolve_name(payment_id, *hrn, &OsRng).map_err(|_| err)?;
186		let context = MessageContext::DNSResolver(dns_context);
187
188		let (send, recv) = channel();
189		{
190			let mut pending_resolutions = self.pending_resolutions.lock().unwrap();
191			let senders = pending_resolutions.entry(*hrn).or_insert_with(Vec::new);
192			senders.push((payment_id, send));
193
194			// If we're running in no-std, we won't expire lookups with the time updates above, so walk
195			// the pending resolution list and expire them here.
196			pending_resolutions.retain(|_name, resolutions| {
197				resolutions.retain(|(_payment_id, resolution)| {
198					let has_receiver = resolution.receiver_alive();
199					if !has_receiver {
200						// TODO: Once LDK 0.2 ships, expire the pending resolution in the resolver:
201						// self.resolver.expire_pending_resolution(name, payment_id);
202					}
203					has_receiver
204				});
205				!resolutions.is_empty()
206			});
207		}
208
209		{
210			let mut queue = self.message_queue.lock().unwrap();
211			for destination in dns_resolvers {
212				let instructions = MessageSendInstructions::WithReplyPath {
213					destination,
214					context: context.clone(),
215				};
216				queue.push((DNSResolverMessage::DNSSECQuery(query.clone()), instructions));
217			}
218		}
219
220		let callback = self.pm_event_poker.read().unwrap();
221		if let Some(callback) = &*callback {
222			callback();
223		}
224
225		Ok(recv)
226	}
227}
228
229impl<N: Deref<Target = NetworkGraph<L>>, L: Deref> DNSResolverMessageHandler
230	for LDKOnionMessageDNSSECHrnResolver<N, L>
231where
232	L::Target: Logger,
233{
234	fn handle_dnssec_query(
235		&self, _: DNSSECQuery, _: Option<Responder>,
236	) -> Option<(DNSResolverMessage, ResponseInstruction)> {
237		None
238	}
239
240	fn handle_dnssec_proof(&self, msg: DNSSECProof, context: DNSResolverContext) {
241		let results = self.resolver.handle_dnssec_proof_for_uri(msg.clone(), context);
242		if let Some((resolved, res)) = results {
243			let mut pending_resolutions = self.pending_resolutions.lock().unwrap();
244			for (name, _payment_id) in resolved {
245				if let Some(requests) = pending_resolutions.remove(&name) {
246					for (_id, send) in requests {
247						send.complete(HrnResolution::DNSSEC {
248							proof: Some(msg.proof.clone()),
249							result: res.clone(),
250						});
251					}
252				}
253			}
254		}
255	}
256
257	fn release_pending_messages(&self) -> Vec<(DNSResolverMessage, MessageSendInstructions)> {
258		std::mem::take(&mut self.message_queue.lock().unwrap())
259	}
260}
261
262impl<N: Deref<Target = NetworkGraph<L>> + Sync, L: Deref> HrnResolver
263	for LDKOnionMessageDNSSECHrnResolver<N, L>
264where
265	L::Target: Logger,
266{
267	fn resolve_hrn<'a>(&'a self, hrn: &'a HumanReadableName) -> HrnResolutionFuture<'a> {
268		match self.init_resolve_hrn(hrn) {
269			Err(e) => Box::pin(async move { Err(e) }),
270			Ok(recv) => Box::pin(async move { Ok(recv.await) }),
271		}
272	}
273
274	fn resolve_lnurl<'a>(&'a self, _url: &'a str) -> HrnResolutionFuture<'a> {
275		let err = "DNS resolver does not support LNURL resolution";
276		Box::pin(async move { Err(err) })
277	}
278
279	fn resolve_lnurl_to_invoice<'a>(
280		&'a self, _: String, _: Amount, _: [u8; 32],
281	) -> LNURLResolutionFuture<'a> {
282		let err = "resolve_lnurl_to_invoice shouldn't be called when we don't resolve LNURL";
283		debug_assert!(false, "{err}");
284		Box::pin(async move { Err(err) })
285	}
286}
287
288#[cfg(test)]
289mod tests {
290	use super::*;
291	use crate::*;
292
293	use std::net::ToSocketAddrs;
294
295	use bitcoin::hex::FromHex;
296	use bitcoin::secp256k1::PublicKey;
297
298	use lightning::blinded_path::NodeIdLookUp;
299	use lightning::ln::peer_handler::{
300		ErroringMessageHandler, IgnoringMessageHandler, MessageHandler, PeerManager,
301	};
302	use lightning::onion_message::messenger::{DefaultMessageRouter, OnionMessenger};
303	use lightning::routing::gossip::{NodeId, P2PGossipSync};
304	use lightning::routing::utxo::UtxoLookup;
305	use lightning::sign::KeysManager;
306	use lightning::util::logger::Record;
307
308	struct TestLogger;
309	impl Logger for TestLogger {
310		fn log(&self, r: Record) {
311			eprintln!("{}", r.args);
312		}
313	}
314
315	struct NoPeers;
316	impl NodeIdLookUp for NoPeers {
317		fn next_node_id(&self, _scid: u64) -> Option<PublicKey> {
318			None
319		}
320	}
321
322	#[tokio::test]
323	async fn test_dns_om_hrn_resolver() {
324		let graph = Arc::new(NetworkGraph::new(Network::Bitcoin, &TestLogger));
325		let resolver = Arc::new(LDKOnionMessageDNSSECHrnResolver::new(Arc::clone(&graph)));
326		let signer = Arc::new(KeysManager::new(&OsRng.get_secure_random_bytes(), 0, 0, true));
327		let message_router = Arc::new(DefaultMessageRouter::new(Arc::clone(&graph), &OsRng));
328		let messenger = Arc::new(OnionMessenger::new(
329			&OsRng,
330			Arc::clone(&signer),
331			&TestLogger,
332			&NoPeers,
333			message_router,
334			&IgnoringMessageHandler {},
335			&IgnoringMessageHandler {},
336			Arc::clone(&resolver),
337			&IgnoringMessageHandler {},
338		));
339		let no_utxos = None::<&(dyn UtxoLookup + Sync + Send)>;
340		let handlers = MessageHandler {
341			chan_handler: Arc::new(ErroringMessageHandler::new()),
342			route_handler: Arc::new(P2PGossipSync::new(Arc::clone(&graph), no_utxos, &TestLogger)),
343			onion_message_handler: Arc::clone(&messenger),
344			custom_message_handler: &IgnoringMessageHandler {},
345			send_only_message_handler: &IgnoringMessageHandler {},
346		};
347		let rand = OsRng.get_secure_random_bytes();
348		let peer_manager =
349			Arc::new(PeerManager::new(handlers, 0, &rand, &TestLogger, Arc::clone(&signer)));
350
351		// Maintain a connection to a static LDK node which we know will do DNS resolutions for us.
352		let their_id_hex = "03db10aa09ff04d3568b0621750794063df401e6853c79a21a83e1a3f3b5bfb0c8";
353		let their_id = PublicKey::from_slice(&Vec::<u8>::from_hex(their_id_hex).unwrap()).unwrap();
354		let addr = "ldk-ln-node.bitcoin.ninja:9735".to_socket_addrs().unwrap().next().unwrap();
355		let connect_pm = Arc::clone(&peer_manager);
356		tokio::spawn(async move {
357			loop {
358				lightning_net_tokio::connect_outbound(Arc::clone(&connect_pm), their_id, addr)
359					.await
360					.unwrap()
361					.await;
362			}
363		});
364
365		let pm_reference = Arc::clone(&peer_manager);
366		tokio::spawn(async move {
367			pm_reference.process_events();
368			tokio::time::sleep(Duration::from_micros(10)).await;
369		});
370
371		let their_node_id = NodeId::from_pubkey(&their_id);
372		loop {
373			{
374				let graph = graph.read_only();
375				let have_announcement =
376					graph.nodes().get(&their_node_id).map(|node| node.announcement_info.is_some());
377				if have_announcement.unwrap_or(false) {
378					break;
379				}
380			}
381			tokio::time::sleep(Duration::from_millis(5)).await;
382			peer_manager.process_events();
383		}
384
385		let instructions = PaymentInstructions::parse(
386			"send.some@satsto.me",
387			bitcoin::Network::Bitcoin,
388			&*resolver,
389			true,
390		)
391		.await
392		.unwrap();
393
394		let resolved = if let PaymentInstructions::ConfigurableAmount(instr) = instructions {
395			assert_eq!(instr.min_amt(), None);
396			assert_eq!(instr.max_amt(), None);
397
398			assert_eq!(instr.pop_callback(), None);
399			assert!(instr.bip_353_dnssec_proof().is_some());
400
401			let hrn = instr.human_readable_name().as_ref().unwrap();
402			assert_eq!(hrn.user(), "send.some");
403			assert_eq!(hrn.domain(), "satsto.me");
404
405			instr.set_amount(Amount::from_sats(100_000).unwrap(), &*resolver).await.unwrap()
406		} else {
407			panic!();
408		};
409
410		assert_eq!(resolved.pop_callback(), None);
411		assert!(resolved.bip_353_dnssec_proof().is_some());
412
413		let hrn = resolved.human_readable_name().as_ref().unwrap();
414		assert_eq!(hrn.user(), "send.some");
415		assert_eq!(hrn.domain(), "satsto.me");
416
417		for method in resolved.methods() {
418			match method {
419				PaymentMethod::LightningBolt11(_) => {
420					panic!("Should only have static payment instructions");
421				},
422				PaymentMethod::LightningBolt12(_) => {},
423				PaymentMethod::OnChain { .. } => {},
424			}
425		}
426	}
427}