Skip to main content

fedimint_tonic_lnd/
lib.rs

1/// This is part of public interface so it's re-exported.
2pub use tonic;
3
4pub use error::ConnectError;
5use error::InternalConnectError;
6use hyper::Uri;
7use hyper_util::client::legacy::connect::HttpConnector;
8use std::path::{Path, PathBuf};
9use std::str::FromStr;
10use tonic::codegen::InterceptedService;
11
12type Service = InterceptedService<
13    hyper_util::client::legacy::Client<
14        hyper_rustls::HttpsConnector<HttpConnector>,
15        tonic::body::Body,
16    >,
17    MacaroonInterceptor,
18>;
19
20/// Convenience type alias for lightning client.
21#[cfg(feature = "lightningrpc")]
22pub type LightningClient = lnrpc::lightning_client::LightningClient<Service>;
23
24/// Convenience type alias for wallet client.
25#[cfg(feature = "walletrpc")]
26pub type WalletKitClient = walletrpc::wallet_kit_client::WalletKitClient<Service>;
27
28/// Convenience type alias for peers service client.
29#[cfg(feature = "peersrpc")]
30pub type PeersClient = peersrpc::peers_client::PeersClient<Service>;
31
32/// Convenience type alias for versioner service client.
33#[cfg(feature = "versionrpc")]
34pub type VersionerClient = verrpc::versioner_client::VersionerClient<Service>;
35
36/// Convenience type alias for signer client.
37#[cfg(feature = "signrpc")]
38pub type SignerClient = signrpc::signer_client::SignerClient<Service>;
39
40/// Convenience type alias for router client.
41#[cfg(feature = "routerrpc")]
42pub type RouterClient = routerrpc::router_client::RouterClient<Service>;
43
44/// Convenience type alias for invoices client.
45#[cfg(feature = "invoicesrpc")]
46pub type InvoicesClient = invoicesrpc::invoices_client::InvoicesClient<Service>;
47
48/// Convenience type alias for state service client.
49#[cfg(feature = "staterpc")]
50pub type StateClient = staterpc::state_client::StateClient<Service>;
51
52/// The client returned by `connect` function
53///
54/// This is a convenience type which you most likely want to use instead of raw client.
55#[derive(Clone)]
56pub struct Client {
57    #[cfg(feature = "lightningrpc")]
58    lightning: LightningClient,
59    #[cfg(feature = "walletrpc")]
60    wallet: WalletKitClient,
61    #[cfg(feature = "signrpc")]
62    signer: SignerClient,
63    #[cfg(feature = "peersrpc")]
64    peers: PeersClient,
65    #[cfg(feature = "versionrpc")]
66    version: VersionerClient,
67    #[cfg(feature = "routerrpc")]
68    router: RouterClient,
69    #[cfg(feature = "invoicesrpc")]
70    invoices: InvoicesClient,
71    #[cfg(feature = "staterpc")]
72    state: StateClient,
73}
74
75impl Client {
76    /// Returns the lightning client.
77    #[cfg(feature = "lightningrpc")]
78    pub fn lightning(&mut self) -> &mut LightningClient {
79        &mut self.lightning
80    }
81
82    /// Returns the wallet client.
83    #[cfg(feature = "walletrpc")]
84    pub fn wallet(&mut self) -> &mut WalletKitClient {
85        &mut self.wallet
86    }
87
88    /// Returns the signer client.
89    #[cfg(feature = "signrpc")]
90    pub fn signer(&mut self) -> &mut SignerClient {
91        &mut self.signer
92    }
93
94    /// Returns the versioner client.
95    #[cfg(feature = "versionrpc")]
96    pub fn versioner(&mut self) -> &mut VersionerClient {
97        &mut self.version
98    }
99
100    /// Returns the peers client.
101    #[cfg(feature = "peersrpc")]
102    pub fn peers(&mut self) -> &mut PeersClient {
103        &mut self.peers
104    }
105
106    /// Returns the router client.
107    #[cfg(feature = "routerrpc")]
108    pub fn router(&mut self) -> &mut RouterClient {
109        &mut self.router
110    }
111
112    /// Returns the invoices client.
113    #[cfg(feature = "invoicesrpc")]
114    pub fn invoices(&mut self) -> &mut InvoicesClient {
115        &mut self.invoices
116    }
117
118    /// Returns the state service client.
119    #[cfg(feature = "staterpc")]
120    pub fn state(&mut self) -> &mut StateClient {
121        &mut self.state
122    }
123}
124
125/// [`tonic::Status`] is re-exported as `Error` for convenience.
126pub type Error = tonic::Status;
127
128mod error;
129
130macro_rules! try_map_err {
131    ($result:expr, $mapfn:expr) => {
132        match $result {
133            Ok(value) => value,
134            Err(error) => return Err($mapfn(error).into()),
135        }
136    };
137}
138
139/// Messages and other types generated by `tonic`/`prost`
140///
141/// This is the go-to module you will need to look in to find documentation on various message
142/// types. However it may be better to start from methods on the [`LightningClient`](lnrpc::lightning_client::LightningClient) type.
143#[cfg(feature = "lightningrpc")]
144pub mod lnrpc {
145    tonic::include_proto!("lnrpc");
146}
147
148#[cfg(feature = "walletrpc")]
149pub mod walletrpc {
150    tonic::include_proto!("walletrpc");
151}
152
153#[cfg(feature = "signrpc")]
154pub mod signrpc {
155    tonic::include_proto!("signrpc");
156}
157
158#[cfg(feature = "peersrpc")]
159pub mod peersrpc {
160    tonic::include_proto!("peersrpc");
161}
162
163#[cfg(feature = "routerrpc")]
164pub mod routerrpc {
165    tonic::include_proto!("routerrpc");
166}
167
168#[cfg(feature = "versionrpc")]
169pub mod verrpc {
170    tonic::include_proto!("verrpc");
171}
172
173#[cfg(feature = "invoicesrpc")]
174pub mod invoicesrpc {
175    tonic::include_proto!("invoicesrpc");
176}
177
178#[cfg(feature = "staterpc")]
179pub mod staterpc {
180    tonic::include_proto!("staterpc");
181}
182
183/// Supplies requests with macaroon
184#[derive(Clone)]
185pub struct MacaroonInterceptor {
186    macaroon: String,
187}
188
189impl tonic::service::Interceptor for MacaroonInterceptor {
190    fn call(&mut self, mut request: tonic::Request<()>) -> Result<tonic::Request<()>, Error> {
191        request.metadata_mut().insert(
192            "macaroon",
193            tonic::metadata::MetadataValue::from_str(&self.macaroon)
194                .expect("hex produced non-ascii"),
195        );
196        Ok(request)
197    }
198}
199
200async fn load_macaroon(
201    path: impl AsRef<Path> + Into<PathBuf>,
202) -> Result<String, InternalConnectError> {
203    let macaroon =
204        tokio::fs::read(&path)
205            .await
206            .map_err(|error| InternalConnectError::ReadFile {
207                file: path.into(),
208                error,
209            })?;
210    Ok(hex::encode(macaroon))
211}
212
213/// Connects to LND using given address and credentials
214///
215/// This function does all required processing of the cert file and macaroon file, so that you
216/// don't have to. The address must begin with "https://", though.
217///
218/// This is considered the recommended way to connect to LND. An alternative function to use
219/// already-read certificate or macaroon data is currently **not** provided to discourage such use.
220/// LND occasionally changes that data which would lead to errors and in turn in worse application.
221///
222/// If you have a motivating use case for use of direct data feel free to open an issue and
223/// explain.
224#[cfg_attr(feature = "tracing", tracing::instrument(name = "Connecting to LND"))]
225pub async fn connect<CP, MP>(
226    address: String,
227    cert_file: CP,
228    macaroon_file: MP,
229) -> Result<Client, ConnectError>
230where
231    CP: AsRef<Path> + Into<PathBuf> + std::fmt::Debug,
232    MP: AsRef<Path> + Into<PathBuf> + std::fmt::Debug,
233{
234    let connector = hyper_rustls::HttpsConnectorBuilder::new()
235        .with_tls_config(tls::config(cert_file).await?)
236        .https_or_http()
237        .enable_http2()
238        .build();
239    let client = hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new())
240        .build(connector);
241
242    // The client implements Service trait directly
243    let macaroon = load_macaroon(macaroon_file).await?;
244
245    let svc = InterceptedService::new(client, MacaroonInterceptor { macaroon });
246    let uri =
247        Uri::from_str(address.as_str()).map_err(|error| InternalConnectError::InvalidAddress {
248            address,
249            error: Box::new(error),
250        })?;
251
252    let client = Client {
253        #[cfg(feature = "lightningrpc")]
254        lightning: lnrpc::lightning_client::LightningClient::with_origin(svc.clone(), uri.clone()),
255        #[cfg(feature = "walletrpc")]
256        wallet: walletrpc::wallet_kit_client::WalletKitClient::with_origin(
257            svc.clone(),
258            uri.clone(),
259        ),
260        #[cfg(feature = "peersrpc")]
261        peers: peersrpc::peers_client::PeersClient::with_origin(svc.clone(), uri.clone()),
262        #[cfg(feature = "signrpc")]
263        signer: signrpc::signer_client::SignerClient::with_origin(svc.clone(), uri.clone()),
264        #[cfg(feature = "versionrpc")]
265        version: verrpc::versioner_client::VersionerClient::with_origin(svc.clone(), uri.clone()),
266        #[cfg(feature = "routerrpc")]
267        router: routerrpc::router_client::RouterClient::with_origin(svc.clone(), uri.clone()),
268        #[cfg(feature = "invoicesrpc")]
269        invoices: invoicesrpc::invoices_client::InvoicesClient::with_origin(
270            svc.clone(),
271            uri.clone(),
272        ),
273        #[cfg(feature = "staterpc")]
274        state: staterpc::state_client::StateClient::with_origin(svc.clone(), uri.clone()),
275    };
276    Ok(client)
277}
278
279mod tls {
280    use crate::error::{ConnectError, InternalConnectError};
281    use rustls::client::danger::ServerCertVerifier;
282    use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified};
283    use rustls::crypto::CryptoProvider;
284    use rustls::crypto::WebPkiSupportedAlgorithms;
285    use rustls::pki_types::{CertificateDer, ServerName, UnixTime};
286    use rustls::{client::ClientConfig, DigitallySignedStruct, Error as TLSError, SignatureScheme};
287    use std::{
288        path::{Path, PathBuf},
289        sync::Arc,
290    };
291
292    pub(crate) async fn config(
293        path: impl AsRef<Path> + Into<PathBuf>,
294    ) -> Result<ClientConfig, ConnectError> {
295        Ok(ClientConfig::builder()
296            .dangerous()
297            .with_custom_certificate_verifier(Arc::new(CertVerifier::load(path).await?))
298            .with_no_client_auth())
299    }
300
301    #[derive(Debug)]
302    pub(crate) struct CertVerifier {
303        certs: Vec<Vec<u8>>,
304        supported_algs: WebPkiSupportedAlgorithms,
305    }
306
307    impl CertVerifier {
308        pub(crate) async fn load(
309            path: impl AsRef<Path> + Into<PathBuf>,
310        ) -> Result<Self, InternalConnectError> {
311            let contents = try_map_err!(tokio::fs::read(&path).await, |error| {
312                InternalConnectError::ReadFile {
313                    file: path.into(),
314                    error,
315                }
316            });
317            let mut reader = &*contents;
318
319            let cert_der_vec = rustls_pemfile::certs(&mut reader).map_err(|error| {
320                InternalConnectError::ParseCert {
321                    file: path.into(),
322                    error,
323                }
324            })?;
325            let certs: Vec<Vec<u8>> = cert_der_vec
326                .into_iter()
327                .map(|cert_der| cert_der.to_vec())
328                .collect();
329
330            let provider = CryptoProvider::get_default()
331                .clone()
332                .expect("Must install default crypto provider");
333
334            Ok(CertVerifier {
335                certs,
336                supported_algs: provider.signature_verification_algorithms,
337            })
338        }
339    }
340
341    impl ServerCertVerifier for CertVerifier {
342        fn verify_server_cert(
343            &self,
344            end_entity: &CertificateDer,
345            intermediates: &[CertificateDer],
346            _server_name: &ServerName,
347            _ocsp_response: &[u8],
348            _now: UnixTime,
349        ) -> Result<ServerCertVerified, TLSError> {
350            let mut certs = intermediates
351                .iter()
352                .map(|c| c.as_ref().to_vec())
353                .collect::<Vec<Vec<u8>>>();
354            certs.push(end_entity.as_ref().to_vec());
355            certs.sort();
356
357            let mut our_certs = self.certs.clone();
358            our_certs.sort();
359
360            if self.certs.len() != certs.len() {
361                return Err(TLSError::General(format!(
362                    "Mismatched number of certificates (Expected: {}, Presented: {})",
363                    self.certs.len(),
364                    certs.len()
365                )));
366            }
367            for (c, p) in our_certs.iter().zip(certs.iter()) {
368                if *p != *c {
369                    return Err(TLSError::General(
370                        "Server certificates do not match ours".to_string(),
371                    ));
372                }
373            }
374            Ok(ServerCertVerified::assertion())
375        }
376
377        fn verify_tls12_signature(
378            &self,
379            message: &[u8],
380            cert: &CertificateDer,
381            dss: &DigitallySignedStruct,
382        ) -> Result<HandshakeSignatureValid, TLSError> {
383            rustls::crypto::verify_tls12_signature(message, cert, dss, &self.supported_algs)
384        }
385
386        fn verify_tls13_signature(
387            &self,
388            message: &[u8],
389            cert: &CertificateDer,
390            dss: &DigitallySignedStruct,
391        ) -> Result<HandshakeSignatureValid, TLSError> {
392            rustls::crypto::verify_tls13_signature(message, cert, dss, &self.supported_algs)
393        }
394
395        fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
396            self.supported_algs.supported_schemes()
397        }
398    }
399}