[dependencies]
log = "^0.4"
+bitcoin = { version = "0.23", features = ["use-serde"] }
serde = { version = "^1.0", features = ["derive"] }
serde_json = { version = "^1.0" }
+
+# Optional dependencies
socks = { version = "^0.3", optional = true }
openssl = { version = "^0.10", optional = true }
-
-[dependencies.bitcoin]
-version = "0.23"
-features = ["use-serde"]
+rustls = { version = "0.16.0", optional = true, features = ["dangerous_configuration"] }
+webpki = { version = "0.21.0", optional = true }
+webpki-roots = { version = "^0.19", optional = true }
[features]
+default = ["socks", "webpki", "webpki-roots", "rustls"]
+minimal = []
debug-calls = []
proxy = ["socks"]
-ssl = ["openssl"]
+use-rustls = ["webpki", "webpki-roots", "rustls"]
+use-openssl = ["openssl"]
--- /dev/null
+extern crate electrum_client;
+
+use electrum_client::Client;
+
+fn main() {
+ // NOTE: This assumes Tor is running localy, with an unauthenticated Socks5 listening at
+ // localhost:9050
+
+ let mut client = Client::new_proxy("ozahtqwp25chjdjd.onion:50001", "127.0.0.1:9050").unwrap();
+ let res = client.server_features();
+ println!("{:#?}", res);
+
+ // works both with onion v2/v3 (if your Tor supports them)
+ let mut client = Client::new_proxy(
+ "v7gtzf7nua6hdmb2wtqaqioqmesdb4xrlly4zwr7bvayxv2bpg665pqd.onion:50001",
+ "127.0.0.1:9050",
+ )
+ .unwrap();
+ let res = client.server_features();
+ println!("{:#?}", res);
+}
use bitcoin::hashes::hex::{FromHex, ToHex};
use bitcoin::{Script, Txid};
-#[cfg(feature = "ssl")]
+#[cfg(feature = "use-openssl")]
use openssl::ssl::{SslConnector, SslMethod, SslStream, SslVerifyMode};
+#[cfg(all(
+ any(feature = "default", feature = "use-rustls"),
+ not(feature = "use-openssl")
+))]
+use rustls::{ClientConfig, ClientSession, StreamOwned};
-#[cfg(feature = "socks")]
+#[cfg(any(feature = "default", feature = "proxy"))]
use socks::{Socks5Stream, ToTargetAddr};
-#[cfg(any(feature = "socks", feature = "proxy"))]
+#[cfg(any(
+ feature = "default",
+ feature = "use-rustls",
+ feature = "use-openssl",
+ feature = "proxy"
+))]
use stream::ClonableStream;
use batch::Batch;
}
}
-#[cfg(feature = "ssl")]
+#[cfg(feature = "use-openssl")]
impl Client<ClonableStream<SslStream<TcpStream>>> {
pub fn new_ssl<A: ToSocketAddrs>(socket_addr: A, domain: Option<&str>) -> Result<Self, Error> {
let mut builder =
}
}
-#[cfg(feature = "proxy")]
+#[cfg(all(
+ any(feature = "default", feature = "use-rustls"),
+ not(feature = "use-openssl")
+))]
+mod danger {
+ use rustls;
+ use webpki;
+
+ pub struct NoCertificateVerification {}
+
+ impl rustls::ServerCertVerifier for NoCertificateVerification {
+ fn verify_server_cert(
+ &self,
+ _roots: &rustls::RootCertStore,
+ _presented_certs: &[rustls::Certificate],
+ _dns_name: webpki::DNSNameRef<'_>,
+ _ocsp: &[u8],
+ ) -> Result<rustls::ServerCertVerified, rustls::TLSError> {
+ Ok(rustls::ServerCertVerified::assertion())
+ }
+ }
+}
+
+#[cfg(all(
+ any(feature = "default", feature = "use-rustls"),
+ not(feature = "use-openssl")
+))]
+impl Client<ClonableStream<StreamOwned<ClientSession, TcpStream>>> {
+ pub fn new_ssl<A: ToSocketAddrs>(socket_addr: A, domain: Option<&str>) -> Result<Self, Error> {
+ let mut config = ClientConfig::new();
+ if domain.is_none() {
+ config
+ .dangerous()
+ .set_certificate_verifier(std::sync::Arc::new(danger::NoCertificateVerification {}))
+ } else {
+ // TODO: cert pinning
+ config
+ .root_store
+ .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
+ }
+
+ let tcp_stream = TcpStream::connect(socket_addr)?;
+ let session = ClientSession::new(
+ &std::sync::Arc::new(config),
+ webpki::DNSNameRef::try_from_ascii_str(domain.unwrap_or("not.validated"))
+ .map_err(|_| Error::InvalidDNSNameError(domain.unwrap_or("<NONE>").to_string()))?,
+ );
+ let stream = StreamOwned::new(session, tcp_stream);
+ let stream: ClonableStream<_> = stream.into();
+
+ let buf_reader = BufReader::new(stream.clone());
+
+ Ok(Self {
+ stream,
+ buf_reader,
+ headers: VecDeque::new(),
+ script_notifications: BTreeMap::new(),
+
+ #[cfg(feature = "debug-calls")]
+ calls: 0,
+ })
+ }
+}
+
+#[cfg(any(feature = "default", feature = "proxy"))]
impl Client<ClonableStream<Socks5Stream>> {
pub fn new_proxy<A: ToSocketAddrs, T: ToTargetAddr>(
target_addr: T,
}
#[cfg(feature = "debug-calls")]
- pub fn calls_made(&self) -> u32 {
+ pub fn calls_made(&self) -> usize {
self.calls
}
pub extern crate bitcoin;
extern crate log;
-#[cfg(feature = "ssl")]
+#[cfg(feature = "use-openssl")]
extern crate openssl;
+#[cfg(all(
+ any(feature = "default", feature = "use-rustls"),
+ not(feature = "use-openssl")
+))]
+extern crate rustls;
extern crate serde;
extern crate serde_json;
-#[cfg(feature = "proxy")]
+#[cfg(any(feature = "default", feature = "proxy"))]
extern crate socks;
+#[cfg(any(feature = "use-rustls", feature = "default"))]
+extern crate webpki;
+#[cfg(any(feature = "use-rustls", feature = "default"))]
+extern crate webpki_roots;
pub mod batch;
pub mod client;
-#[cfg(any(feature = "socks", feature = "proxy"))]
+#[cfg(any(
+ feature = "default",
+ feature = "use-rustls",
+ feature = "use-openssl",
+ feature = "proxy"
+))]
mod stream;
#[cfg(test)]
mod test_stream;