Initial checkin
[lightning-resolver] / src / lib.rs
1 //! 
2
3 #[deny(missing_docs)]
4
5 #[cfg(feature = "domain_resolver")]
6 use std::net::SocketAddr;
7
8 use std::sync::Mutex; // XXX: wont work without, should support no-std
9
10 extern crate alloc;
11 extern crate core;
12
13 use core::str::FromStr;
14
15 #[cfg(feature = "domain_resolver")]
16 use alloc::sync::Arc;
17 #[cfg(any(feature = "name_resolver", feature = "domain_resolver"))]
18 use core::sync::atomic::{AtomicUsize, Ordering};
19
20 use dnssec_prover::rr::Name;
21
22 #[cfg(feature = "domain_resolver")]
23 use dnssec_prover::query::build_txt_proof;
24
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;
29
30 #[cfg(any(feature = "domain_resolver", feature = "name_resolver"))]
31 use lightning::onion_message::messenger::{CustomOnionMessageHandler, PendingOnionMessage};
32
33 #[cfg(feature = "name_resolver")]
34 use lightning::blinded_path::BlindedPath;
35 #[cfg(feature = "name_resolver")]
36 use lightning::onion_message::messenger::Destination;
37
38 #[derive(Debug)]
39 pub enum ResolverMessages {
40         DNSSECQuery(DNSSECQuery),
41         DNSSECProof(DNSSECProof),
42         OfferRequest(OfferRequest),
43         OfferResponse(OfferResponse),
44 }
45
46 #[derive(Debug)]
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;
50
51 #[derive(Debug)]
52 pub struct DNSSECProof {
53         pub name: Name,
54         pub proof: Vec<u8>,
55 }
56 const DNSSEC_PROOF_TYPE: u64 = 65538;
57
58 #[derive(Debug)]
59 pub struct OfferRequest {
60         pub user: String,
61         pub domain: String,
62 }
63 const OFFER_REQUEST_TYPE: u64 = 44; // XXX
64
65 #[derive(Debug)]
66 pub struct OfferResponse {
67         pub user: String,
68         pub domain: String,
69         pub offer: Offer,
70 }
71 const OFFER_RESPONSE_TYPE: u64 = 45; // XXX
72
73 impl Writeable for ResolverMessages {
74         fn write<W: Writer>(&self, w: &mut W) -> Result<(), lightning::io::Error> {
75                 match self {
76                         Self::DNSSECQuery(DNSSECQuery(q)) => {
77                                 (q.as_str().len() as u8).write(w)?;
78                                 w.write_all(&q.as_str().as_bytes())
79                         },
80                         Self::DNSSECProof(DNSSECProof { name, proof }) => {
81                                 (name.as_str().len() as u8).write(w)?;
82                                 w.write_all(&name.as_str().as_bytes())?;
83                                 proof.write(w)
84                         },
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())
90                         },
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)
97                         },
98                 }
99         }
100 }
101
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);
110         }
111         let s = String::from_utf8(bytes[..len as usize].to_vec())
112                 .map_err(|_| DecodeError::InvalidValue)?;
113         Ok(s)
114 }
115
116 impl ResolverMessages {
117         pub fn read_message<R: lightning::io::Read>(message_type: u64, buffer: &mut R)
118         -> Result<Option<ResolverMessages>, DecodeError> {
119                 match message_type {
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))))
124                         },
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 })))
130                         },
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 })))
135                         },
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 })))
143                                 } else {
144                                         Err(DecodeError::InvalidValue)
145                                 }
146                         },
147                         _ => Ok(None),
148                 }
149         }
150 }
151
152 impl OnionMessageContents for ResolverMessages {
153         fn tlv_type(&self) -> u64 {
154                 match self {
155                         ResolverMessages::DNSSECQuery(_) => DNSSEC_QUERY_TYPE,
156                         ResolverMessages::DNSSECProof(_) => DNSSEC_PROOF_TYPE,
157                         ResolverMessages::OfferRequest(_) => OFFER_REQUEST_TYPE,
158                         ResolverMessages::OfferResponse(_) => OFFER_RESPONSE_TYPE,
159                 }
160         }
161 }
162
163 #[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))]
164 const WE_REQUIRE_32_OR_64_BIT_USIZE: u8 = 424242;
165
166 #[cfg(feature = "domain_resolver")]
167 pub struct OMDomainResolver {
168         state: Arc<domain_resolver::OMResolverState>,
169 }
170
171 #[cfg(feature = "domain_resolver")]
172 mod domain_resolver {
173         use super::*;
174
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,
180         }
181
182         impl OMDomainResolver {
183                 pub fn new(resolver: SocketAddr) -> Self {
184                         Self { state: Arc::new(OMResolverState {
185                                 resolver,
186                                 pending_msgs: Mutex::new(Vec::new()),
187                                 pending_query_count: AtomicUsize::new(0),
188                         }) }
189                 }
190         }
191
192         impl CustomOnionMessageHandler for OMDomainResolver {
193                 type CustomMessage = ResolverMessages;
194
195                 fn handle_custom_message(&self, msg: ResolverMessages) -> Option<Self::CustomMessage> {
196                         match msg {
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);
200                                                 return None;
201                                         }
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 {
208                                                                 name: q.0, proof,
209                                                         });
210                                                         us.pending_query_count.fetch_sub(1, Ordering::Relaxed);
211                                                         return Some(contents);
212                                                         /*let response = PendingOnionMessage {
213                                                                 contents,
214                                                                 destination: Destination::BlindedPath(reply_path),
215                                                                 reply_path: None,
216                                                         };
217                                                         us.pending_msgs.lock().unwrap().push(response);*/
218                                                 } else { None }
219                                         //});
220                                 },
221                                 _ => None,
222                         }
223                 }
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)
227                 }
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());
231                         res
232                 }
233         }
234 }
235
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,
244 }
245
246 #[cfg(feature = "name_resolver")]
247 mod name_resolver {
248         use super::*;
249
250         use dnssec_prover::ser::parse_rr_stream;
251         use dnssec_prover::rr::RR;
252         use dnssec_prover::validation::verify_rr_stream;
253
254         use hex_conservative::FromHex;
255
256         impl OMNameResolver {
257                 pub fn new(resolver: Destination, reply_path: BlindedPath, latest_block_time: u32, latest_block_height: u32) -> Self {
258                         Self {
259                                 resolver,
260                                 reply_path,
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),
265                         }
266                 }
267
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 {
274                                         callback(None);
275                                         false
276                                 } else {
277                                         true
278                                 }
279                         });
280                 }
281
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("@") {
284                                 let name_query =
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,
293                                         });
294                                 } else {
295                                         return Err(());
296                                 }
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));
300                                 Ok(())
301                         } else {
302                                 Err(())
303                         }
304                 }
305         }
306
307         impl CustomOnionMessageHandler for OMNameResolver {
308                 type CustomMessage = ResolverMessages;
309
310                 fn handle_custom_message(&self, msg: ResolverMessages) -> Option<Self::CustomMessage> {
311                         match msg {
312                                 ResolverMessages::DNSSECQuery(_) => {},
313                                 ResolverMessages::OfferRequest(_) => {},
314                                 ResolverMessages::DNSSECProof(DNSSECProof { name: answer_name, proof }) => {
315                                         let parsed_rrs = parse_rr_stream(&proof);
316                                         let validated_rrs =
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 {
321                                                         return None;
322                                                 }
323                                                 if validated_rrs.expires < block_time - 60 * 2 {
324                                                         return None;
325                                                 }
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; }
330
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("@") {
335                                                                 r
336                                                         } else {
337                                                                 debug_assert!(false, "This should be checked before insertion");
338                                                                 return false;
339                                                         };
340
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)
346                                                         {
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));
353                                                                 let txt_record =
354                                                                         match (candidate_records.next(), candidate_records.next()) {
355                                                                                 (Some(txt), None) => txt,
356                                                                                 (_, _) => {
357                                                                                         resolution_callback(None);
358                                                                                         return false;
359                                                                                 },
360                                                                         };
361                                                                 let (_onchain, params) = if let Some(split) = txt_record.split_once("?") {
362                                                                         split
363                                                                 } else {
364                                                                         resolution_callback(None);
365                                                                         return false;
366                                                                 };
367                                                                 for param in params.split("&") {
368                                                                         let (k, v) = if let Some(split) = param.split_once("=") {
369                                                                                 split
370                                                                         } else {
371                                                                                 continue;
372                                                                         };
373                                                                         if k.eq_ignore_ascii_case("b12") {
374                                                                                 if let Ok(offer) = Offer::from_str(v) {
375                                                                                         resolution_callback(Some(offer));
376                                                                                 } else {
377                                                                                         resolution_callback(None);
378                                                                                 }
379                                                                                 return false;
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[..])
384                                                                                         .map_err(|_| ()));
385                                                                                 let request_path = if let Ok(path) = request_path {
386                                                                                         path
387                                                                                 } else {
388                                                                                         resolution_callback(None);
389                                                                                         return false;
390                                                                                 };
391                                                                                 let contents = ResolverMessages::OfferRequest(OfferRequest {
392                                                                                         user: user.to_owned(),
393                                                                                         domain: domain.to_owned(),
394                                                                                 });
395                                                                                 let response = PendingOnionMessage {
396                                                                                         contents,
397                                                                                         destination: Destination::BlindedPath(request_path),
398                                                                                         reply_path: Some(self.reply_path.clone()),
399                                                                                 };
400                                                                                 self.pending_msgs.lock().unwrap().push(response);
401                                                                                 return true;
402                                                                         }
403                                                                 }
404                                                                 resolution_callback(None);
405                                                                 false
406                                                         } else {
407                                                                 true
408                                                         }
409                                                 });
410                                         }
411                                 },
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("@") {
416                                                         r
417                                                 } else {
418                                                         debug_assert!(false, "This should be checked before insertion");
419                                                         return false;
420                                                 };
421                                                 if query_user != user || query_domain != domain { return true; }
422                                                 resolution_callback(Some(offer.clone()));
423                                                 false
424                                         });
425                                 },
426                         }
427                         None
428                 }
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)
432                 }
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());
436                         res
437                 }
438         }
439 }
440
441 #[cfg(all(test, feature = "name_resolver", feature = "domain_resolver"))]
442 mod test {
443         use super::*;
444
445         use secp256k1::{Secp256k1, PublicKey};
446
447         use lightning::sign::KeysManager;
448
449         use std::time::SystemTime;
450
451         #[test]
452         fn basic() {
453                 let secp_ctx = Secp256k1::new();
454                 let dummy_pk = PublicKey::from_slice(&[2; 33]).unwrap();
455
456                 let resolver = OMDomainResolver::new("8.8.8.8:53".parse().unwrap());
457
458                 let resolver_keys = KeysManager::new(&[0; 32], 42, 43);
459                 let resolver_dest = Destination::Node(dummy_pk);
460                 let payer_path =
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);
464
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);
471
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();
476
477                 assert!(payer.handle_custom_message(response).is_none());
478                 offer_ref.lock().unwrap().take().unwrap();
479         }
480 }