5 #[cfg(feature = "domain_resolver")]
6 use std::net::SocketAddr;
8 use std::sync::Mutex; // XXX: wont work without, should support no-std
13 use core::str::FromStr;
15 #[cfg(feature = "domain_resolver")]
17 #[cfg(any(feature = "name_resolver", feature = "domain_resolver"))]
18 use core::sync::atomic::{AtomicUsize, Ordering};
20 use dnssec_prover::rr::Name;
22 #[cfg(feature = "domain_resolver")]
23 use dnssec_prover::query::build_txt_proof;
25 use lightning::onion_message::packet::OnionMessageContents;
26 use lightning::util::ser::{Readable, Writeable, Writer};
27 use lightning::ln::msgs::DecodeError;
28 use lightning::offers::offer::Offer;
30 #[cfg(any(feature = "domain_resolver", feature = "name_resolver"))]
31 use lightning::onion_message::messenger::{CustomOnionMessageHandler, PendingOnionMessage};
33 #[cfg(feature = "name_resolver")]
34 use lightning::blinded_path::BlindedPath;
35 #[cfg(feature = "name_resolver")]
36 use lightning::onion_message::messenger::Destination;
39 pub enum ResolverMessages {
40 DNSSECQuery(DNSSECQuery),
41 DNSSECProof(DNSSECProof),
42 OfferRequest(OfferRequest),
43 OfferResponse(OfferResponse),
47 /// A Query for a proof of all TXT records at a given name.
48 pub struct DNSSECQuery(pub Name);
49 const DNSSEC_QUERY_TYPE: u64 = 65536;
52 pub struct DNSSECProof {
56 const DNSSEC_PROOF_TYPE: u64 = 65538;
59 pub struct OfferRequest {
63 const OFFER_REQUEST_TYPE: u64 = 44; // XXX
66 pub struct OfferResponse {
71 const OFFER_RESPONSE_TYPE: u64 = 45; // XXX
73 impl Writeable for ResolverMessages {
74 fn write<W: Writer>(&self, w: &mut W) -> Result<(), lightning::io::Error> {
76 Self::DNSSECQuery(DNSSECQuery(q)) => {
77 (q.as_str().len() as u8).write(w)?;
78 w.write_all(&q.as_str().as_bytes())
80 Self::DNSSECProof(DNSSECProof { name, proof }) => {
81 (name.as_str().len() as u8).write(w)?;
82 w.write_all(&name.as_str().as_bytes())?;
85 Self::OfferRequest(OfferRequest { user, domain }) => {
86 (user.len() as u8).write(w)?;
87 w.write_all(&user.as_bytes())?;
88 (domain.len() as u8).write(w)?;
89 w.write_all(&domain.as_bytes())
91 Self::OfferResponse(OfferResponse { user, domain, offer }) => {
92 (user.len() as u8).write(w)?;
93 w.write_all(&user.as_bytes())?;
94 (domain.len() as u8).write(w)?;
95 w.write_all(&domain.as_bytes())?;
96 offer.to_string().write(w)
102 fn read_byte_len_ascii_string<R: lightning::io::Read>(buffer: &mut R)
103 -> Result<String, DecodeError> {
104 let len: u8 = Readable::read(buffer)?;
105 let mut bytes = [0; 255];
106 buffer.read_exact(&mut bytes[..len as usize])?;
107 if bytes[..len as usize].iter().any(|b| *b < 0x20 || *b > 0x7e) {
108 // If the bytes are not entirely in the printable ASCII range, fail
109 return Err(DecodeError::InvalidValue);
111 let s = String::from_utf8(bytes[..len as usize].to_vec())
112 .map_err(|_| DecodeError::InvalidValue)?;
116 impl ResolverMessages {
117 pub fn read_message<R: lightning::io::Read>(message_type: u64, buffer: &mut R)
118 -> Result<Option<ResolverMessages>, DecodeError> {
120 DNSSEC_QUERY_TYPE => {
121 let s = read_byte_len_ascii_string(buffer)?;
122 let name = s.try_into().map_err(|_| DecodeError::InvalidValue)?;
123 Ok(Some(ResolverMessages::DNSSECQuery(DNSSECQuery(name))))
125 DNSSEC_PROOF_TYPE => {
126 let s = read_byte_len_ascii_string(buffer)?;
127 let name = s.try_into().map_err(|_| DecodeError::InvalidValue)?;
128 let proof = Readable::read(buffer)?;
129 Ok(Some(ResolverMessages::DNSSECProof(DNSSECProof { name, proof })))
131 OFFER_REQUEST_TYPE => {
132 let user = read_byte_len_ascii_string(buffer)?;
133 let domain = read_byte_len_ascii_string(buffer)?;
134 Ok(Some(ResolverMessages::OfferRequest(OfferRequest { user, domain })))
136 OFFER_RESPONSE_TYPE => {
137 let user = read_byte_len_ascii_string(buffer)?;
138 let domain = read_byte_len_ascii_string(buffer)?;
139 let offer_string: String = Readable::read(buffer)?;
140 if !offer_string.is_ascii() { return Err(DecodeError::InvalidValue); }
141 if let Ok(offer) = Offer::from_str(&offer_string) {
142 Ok(Some(ResolverMessages::OfferResponse(OfferResponse { user, domain, offer })))
144 Err(DecodeError::InvalidValue)
152 impl OnionMessageContents for ResolverMessages {
153 fn tlv_type(&self) -> u64 {
155 ResolverMessages::DNSSECQuery(_) => DNSSEC_QUERY_TYPE,
156 ResolverMessages::DNSSECProof(_) => DNSSEC_PROOF_TYPE,
157 ResolverMessages::OfferRequest(_) => OFFER_REQUEST_TYPE,
158 ResolverMessages::OfferResponse(_) => OFFER_RESPONSE_TYPE,
163 #[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))]
164 const WE_REQUIRE_32_OR_64_BIT_USIZE: u8 = 424242;
166 #[cfg(feature = "domain_resolver")]
167 pub struct OMDomainResolver {
168 state: Arc<domain_resolver::OMResolverState>,
171 #[cfg(feature = "domain_resolver")]
172 mod domain_resolver {
175 const MAX_PENDING_RESPONSES: usize = 1024;
176 pub(super) struct OMResolverState {
177 resolver: SocketAddr,
178 pending_msgs: Mutex<Vec<PendingOnionMessage<ResolverMessages>>>,
179 pending_query_count: AtomicUsize,
182 impl OMDomainResolver {
183 pub fn new(resolver: SocketAddr) -> Self {
184 Self { state: Arc::new(OMResolverState {
186 pending_msgs: Mutex::new(Vec::new()),
187 pending_query_count: AtomicUsize::new(0),
192 impl CustomOnionMessageHandler for OMDomainResolver {
193 type CustomMessage = ResolverMessages;
195 fn handle_custom_message(&self, msg: ResolverMessages) -> Option<Self::CustomMessage> {
197 ResolverMessages::DNSSECQuery(q) => {
198 if self.state.pending_query_count.fetch_add(1, Ordering::Relaxed) > MAX_PENDING_RESPONSES {
199 self.state.pending_query_count.fetch_sub(1, Ordering::Relaxed);
202 let us = Arc::clone(&self.state);
203 // TODO: Make this async after https://github.com/lightningdevkit/rust-lightning/issues/2882
204 //tokio::spawn(async move {
205 //if let Ok(proof) = build_txt_proof_async(us.resolver, &q.0).await {
206 if let Ok((proof, _ttl)) = build_txt_proof(us.resolver, &q.0) {
207 let contents = ResolverMessages::DNSSECProof(DNSSECProof {
210 us.pending_query_count.fetch_sub(1, Ordering::Relaxed);
211 return Some(contents);
212 /*let response = PendingOnionMessage {
214 destination: Destination::BlindedPath(reply_path),
217 us.pending_msgs.lock().unwrap().push(response);*/
224 fn read_custom_message<R: std::io::Read>(&self, message_type: u64, buffer: &mut R)
225 -> Result<Option<ResolverMessages>, DecodeError> {
226 ResolverMessages::read_message(message_type, buffer)
228 fn release_pending_custom_messages(&self) -> Vec<PendingOnionMessage<ResolverMessages>> {
229 let mut res = Vec::new();
230 std::mem::swap(&mut res, &mut self.state.pending_msgs.lock().unwrap());
236 #[cfg(feature = "name_resolver")]
237 pub struct OMNameResolver {
238 resolver: Destination,
239 reply_path: BlindedPath,
240 pending_msgs: Mutex<Vec<PendingOnionMessage<ResolverMessages>>>,
241 pending_resolves: Mutex<Vec<(u32, String, Box<dyn FnMut(Option<Offer>)>)>>,
242 latest_block_time: AtomicUsize,
243 latest_block_height: AtomicUsize,
246 #[cfg(feature = "name_resolver")]
250 use dnssec_prover::ser::parse_rr_stream;
251 use dnssec_prover::rr::RR;
252 use dnssec_prover::validation::verify_rr_stream;
254 use hex_conservative::FromHex;
256 impl OMNameResolver {
257 pub fn new(resolver: Destination, reply_path: BlindedPath, latest_block_time: u32, latest_block_height: u32) -> Self {
261 pending_msgs: Mutex::new(Vec::new()),
262 pending_resolves: Mutex::new(Vec::new()),
263 latest_block_time: AtomicUsize::new(latest_block_time as usize),
264 latest_block_height: AtomicUsize::new(latest_block_height as usize),
268 pub fn set_block_time(&self, height: u32, time: u32) {
269 self.latest_block_time.store(time as usize, Ordering::Release);
270 self.latest_block_height.store(height as usize, Ordering::Release);
271 let mut resolves = self.pending_resolves.lock().unwrap();
272 resolves.retain_mut(|(res_height, _, callback)| {
273 if *res_height < height - 1 {
282 pub fn resolve_name(&self, name: String, resolution_callback: Box<dyn FnMut(Option<Offer>)>) -> Result<(), ()> {
283 if let Some((user, domain)) = name.split_once("@") {
285 Name::try_from(format!("{}.user._bitcoin-payment.{}.", user, domain))
286 .map(|q| ResolverMessages::DNSSECQuery(DNSSECQuery(q)));
287 if let Ok(q) = name_query {
288 let mut pending_msgs = self.pending_msgs.lock().unwrap();
289 let destination = self.resolver.clone();
290 let reply_path = Some(self.reply_path.clone());
291 pending_msgs.push(PendingOnionMessage {
292 contents: q, destination, reply_path,
297 let height = self.latest_block_height.load(Ordering::Acquire);
298 let mut pending_resolves = self.pending_resolves.lock().unwrap();
299 pending_resolves.push((height as u32, name, resolution_callback));
307 impl CustomOnionMessageHandler for OMNameResolver {
308 type CustomMessage = ResolverMessages;
310 fn handle_custom_message(&self, msg: ResolverMessages) -> Option<Self::CustomMessage> {
312 ResolverMessages::DNSSECQuery(_) => {},
313 ResolverMessages::OfferRequest(_) => {},
314 ResolverMessages::DNSSECProof(DNSSECProof { name: answer_name, proof }) => {
315 let parsed_rrs = parse_rr_stream(&proof);
317 parsed_rrs.as_ref().and_then(|rrs| verify_rr_stream(rrs).map_err(|_| &()));
318 if let Ok(validated_rrs) = validated_rrs {
319 let block_time = self.latest_block_time.load(Ordering::Acquire) as u64;
320 if validated_rrs.valid_from > block_time + 60 * 2 {
323 if validated_rrs.expires < block_time - 60 * 2 {
326 let qname = answer_name.as_str();
327 if qname.len() <= "._bitcoin_payment.".len() { return None; }
328 let resolved_rrs = validated_rrs.resolve_name(&answer_name);
329 if resolved_rrs.is_empty() { return None; }
331 let mut pending_resolves = self.pending_resolves.lock().unwrap();
332 pending_resolves.retain_mut(|(_height, query, resolution_callback)| {
333 debug_assert_eq!(qname.chars().last().unwrap_or('X'), '.');
334 let (user, domain) = if let Some(r) = query.split_once("@") {
337 debug_assert!(false, "This should be checked before insertion");
341 let expected_len = user.len() + ".user._bitcoin-payment.".len() + domain.len() + 1;
342 if qname.len() == expected_len &&
343 qname.starts_with(user) &&
344 qname[user.len()..].starts_with(".user._bitcoin-payment.") &&
345 qname[..qname.len() - 1].ends_with(domain)
347 const URI_PREFIX: &str = "bitcoin:";
348 let mut candidate_records = resolved_rrs.iter()
349 .filter_map(|rr| if let RR::Txt(txt) = rr { Some(&txt.data) } else { None })
350 .filter_map(|data| if let Ok(s) = core::str::from_utf8(data) { Some(s) } else { None })
351 .filter(|data_string| data_string.len() > URI_PREFIX.len())
352 .filter(|data_string| data_string[..URI_PREFIX.len()].eq_ignore_ascii_case(URI_PREFIX));
354 match (candidate_records.next(), candidate_records.next()) {
355 (Some(txt), None) => txt,
357 resolution_callback(None);
361 let (_onchain, params) = if let Some(split) = txt_record.split_once("?") {
364 resolution_callback(None);
367 for param in params.split("&") {
368 let (k, v) = if let Some(split) = param.split_once("=") {
373 if k.eq_ignore_ascii_case("b12") {
374 if let Ok(offer) = Offer::from_str(v) {
375 resolution_callback(Some(offer));
377 resolution_callback(None);
380 } else if k.eq_ignore_ascii_case("omlookup") {
381 let data_hex = Vec::from_hex(v).map_err(|_| ());
382 let request_path = data_hex
383 .and_then(|data| BlindedPath::read(&mut &data[..])
385 let request_path = if let Ok(path) = request_path {
388 resolution_callback(None);
391 let contents = ResolverMessages::OfferRequest(OfferRequest {
392 user: user.to_owned(),
393 domain: domain.to_owned(),
395 let response = PendingOnionMessage {
397 destination: Destination::BlindedPath(request_path),
398 reply_path: Some(self.reply_path.clone()),
400 self.pending_msgs.lock().unwrap().push(response);
404 resolution_callback(None);
412 ResolverMessages::OfferResponse(OfferResponse { user, domain, offer }) => {
413 let mut pending_resolves = self.pending_resolves.lock().unwrap();
414 pending_resolves.retain_mut(|(_height, query, resolution_callback)| {
415 let (query_user, query_domain) = if let Some(r) = query.split_once("@") {
418 debug_assert!(false, "This should be checked before insertion");
421 if query_user != user || query_domain != domain { return true; }
422 resolution_callback(Some(offer.clone()));
429 fn read_custom_message<R: lightning::io::Read>(&self, message_type: u64, buffer: &mut R)
430 -> Result<Option<ResolverMessages>, DecodeError> {
431 ResolverMessages::read_message(message_type, buffer)
433 fn release_pending_custom_messages(&self) -> Vec<PendingOnionMessage<ResolverMessages>> {
434 let mut res = Vec::new();
435 std::mem::swap(&mut res, &mut self.pending_msgs.lock().unwrap());
441 #[cfg(all(test, feature = "name_resolver", feature = "domain_resolver"))]
445 use secp256k1::{Secp256k1, PublicKey};
447 use lightning::sign::KeysManager;
449 use std::time::SystemTime;
453 let secp_ctx = Secp256k1::new();
454 let dummy_pk = PublicKey::from_slice(&[2; 33]).unwrap();
456 let resolver = OMDomainResolver::new("8.8.8.8:53".parse().unwrap());
458 let resolver_keys = KeysManager::new(&[0; 32], 42, 43);
459 let resolver_dest = Destination::Node(dummy_pk);
461 BlindedPath::one_hop_for_message(dummy_pk, &resolver_keys, &secp_ctx).unwrap();
462 let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
463 let payer = OMNameResolver::new(resolver_dest.clone(), payer_path.clone(), now as u32, 1);
465 let resolved_offer = Arc::new(Mutex::new(None));
466 let offer_ref = Arc::clone(&resolved_offer);
467 let resolve_offer = Box::new(move |offer| *resolved_offer.lock().unwrap() = offer);
468 payer.resolve_name("matt@mattcorallo.com".to_owned(), resolve_offer).unwrap();
469 let mut msgs = payer.release_pending_custom_messages();
470 assert_eq!(msgs.len(), 1);
472 let msg = msgs.pop().unwrap();
473 assert_eq!(msg.destination, resolver_dest);
474 assert_eq!(msg.reply_path, Some(payer_path));
475 let response = resolver.handle_custom_message(msg.contents).unwrap();
477 assert!(payer.handle_custom_message(response).is_none());
478 offer_ref.lock().unwrap().take().unwrap();