diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index dbff91d14e..1c9667b38a 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -60,7 +60,8 @@ const tutorialSidebar = [ '/tutorials/using_bdk_with_hardware_wallets', '/tutorials/exploring_bdk_flutter', '/tutorials/bdk_cli_basics', - '/tutorials/bdk-cli_basics_multisig_2of3' + '/tutorials/bdk-cli_basics_multisig_2of3', + '/tutorials/bdk_with_tor', ], } ] diff --git a/docs/tutorials/bdk_with_tor.md b/docs/tutorials/bdk_with_tor.md new file mode 100644 index 0000000000..438db2271b --- /dev/null +++ b/docs/tutorials/bdk_with_tor.md @@ -0,0 +1,406 @@ +--- +title: "Using BDK with Tor" +description: "How to integrate Tor client to sync BDK wallet with tor enabled blockchain service" +authors: + - rorp +date: "2023-01-03" +tags: ["tutorial", "tor", "wallet", "blockchain"] +--- + +## Introduction + +It’s easy to underestimate the importance of privacy tech for Bitcoin, +especially when connecting to third party services. They can learn your +IP address and associate the transactions you sent over it. You can only +hope that this information will not be leaked anytime in the future with +unpredictable consequences. In order to use Bitcoin privately, you need +to encrypt and anonymize the data you send over the Internet. + +Tor is one of the must-have privacy preserving tools for the Internet in +general, and for Bitcoin in particular. Tor network consists of nodes that +use clever cryptographic methods to encrypt user data and transfer them as +anonymously as possible. + +In this article we show how to integrate Tor with your BDK application. + +## Prerequisite + +First, you would need to have a Tor daemon up and running. + +On Mac OS X you can install with Homebrew. + +```bash +brew install tor +brew services start tor +``` + +On Ubuntu or other Debian-based distributions. + +```bash +sudo apt install tor +``` + +In some cases you'll need to wait a minute or two for the bootstrapping to finish. +In general, Tor is not the fastest network, so if any of the examples below fail +due to timeout, simply restart it. + +At the very end of the article we’ll show how to integrate Tor directly to +your application. + +By default, Tor creates a [SOCKS5](https://en.wikipedia.org/wiki/SOCKS) proxy +endpoint and listens on port 9050. Your application should connect to the +proxy on `localhost:9050` and use it for its network activities. + +## Setting Up + +Create a new cargo project. + +```bash +mkdir ~/tutorial +cd tutorial +cargo new bdk-tor +cd bdk-tor +``` + +Open `src/main.rs` file remove all its contents and add these lines. + +```rust +use std::str::FromStr; +use bdk::bitcoin::util::bip32; +use bdk::bitcoin::util::bip32::ExtendedPrivKey; +use bdk::bitcoin::Network; +use bdk::database::MemoryDatabase; +use bdk::template::Bip84; +use bdk::{KeychainKind, SyncOptions, Wallet}; + +// add additional imports here + +fn main() { + let network = Network::Testnet; + + let xpriv = "tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy"; + + let xpriv = bip32::ExtendedPrivKey::from_str(xpriv).unwrap(); + + let blockchain = create_blockchain(); + + let wallet = create_wallet(&network, &xpriv); + + println!("Syncing the wallet..."); + + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + + println!( + "The wallet synced. Height: {}", + blockchain.get_height().unwrap() + ); +} + +fn create_wallet(network: &Network, xpriv: &ExtendedPrivKey) -> Wallet { + Wallet::new( + Bip84(*xpriv, KeychainKind::External), + Some(Bip84(*xpriv, KeychainKind::Internal)), + *network, + MemoryDatabase::default(), + ) + .unwrap() +} +``` + +In this code we create a testnet wallet with `create_wallet()` function and +try to sync it with a specific blockchain client implementation. We create a +blockchain client using `create_blockchain()` function. We’ll implement it +later for each type of blockchain client supported by BDK. + +## ElectrumBlockchain + +The Electrum client is enabled by default so the `Cargo.toml` dependencies +section will look like this. + +```toml +[dependencies] +bdk = { version = "^0.26"} +``` + +And the imports look like this. + +```rust +use bdk::blockchain::{ElectrumBlockchain, GetHeight}; +use bdk::electrum_client::{Client, ConfigBuilder, Socks5Config}; +``` + +Here is the implementation of `create_blockchain()` function for the +Electrum client. + +```rust +fn create_blockchain() -> ElectrumBlockchain { + let url = "ssl://electrum.blockstream.info:60002"; + let socks_addr = "127.0.0.1:9050"; + + println!("Connecting to {} via {}", &url, &socks_addr); + + let config = ConfigBuilder::new() + .socks5(Some(Socks5Config { + addr: socks_addr.to_string(), + credentials: None, + })) + .unwrap() + .build(); + + let client = Client::from_config(url, config).unwrap(); + + ElectrumBlockchain::from(client) +} +``` + +In this example we create an instance of `Socks5Config` which defines the +Tor proxy parameters for `ElectrumBlockchain`. + +## Blocking EsploraBlockchain + +The blocking version of `EsploraBlockchain` uses `ureq` crate to send HTTP +requests to Eslora backends. By default, its SOCKS5 feature is disabled, +so we need to enable it in `Cargo.toml`. + +```toml +[dependencies] +bdk = { version = "^0.26", default-features = false, features = ["use-esplora-blocking"]} +``` + +The imports are + +```rust +use bdk::blockchain::{EsploraBlockchain, GetHeight}; +use bdk::blockchain::esplora::EsploraBlockchainConfig; +use bdk::blockchain::ConfigurableBlockchain; +``` + +And `create_blockchain()` implementation is + +```rust +fn create_blockchain() -> EsploraBlockchain { + let url = "http://mempoolhqx4isw62xs7abwphsq7ldayuidyx2v2oethdhhj6mlo2r6ad.onion/testnet/api"; + let socks_url = "socks5://127.0.0.1:9050"; + + println!("Connecting to {} via {}", &url, &socks_url); + + let config = EsploraBlockchainConfig { + base_url: url.into(), + proxy: Some(socks_url.into()), + concurrency: None, + stop_gap: 20, + timeout: Some(120), + }; + + EsploraBlockchain::from_config(&config).unwrap() +} +``` + +Here we use `proxy()` method of the config builder to set the Tor proxy +address. Please note, that unlike the previous examples, the Esplora client +builder requires not just a proxy address, but a URL +“socks5://127.0.0.1:9050”. + +## Asynchronous EsploraBlockchain + +There’s no need in enabling SOCKS5 for the asynchronous Esplora client, +so we are good to go without additional dependencies. + +```toml +[dependencies] +bdk = { version = "^0.26", default-features = false, features = ["use-esplora-async"]} +``` + +The imports are the same as in previous example. + +```rust +use bdk::blockchain::{EsploraBlockchain, GetHeight}; +use bdk::blockchain::esplora::EsploraBlockchainConfig; +use bdk::blockchain::ConfigurableBlockchain; +``` + +`create_blockchain()` is almost identical. + +```rust +fn create_blockchain() -> EsploraBlockchain { + let url = "http://mempoolhqx4isw62xs7abwphsq7ldayuidyx2v2oethdhhj6mlo2r6ad.onion/testnet/api"; + let socks_url = "socks5h://127.0.0.1:9050"; + + println!("Connecting to {} via {}", &url, &socks_url); + + let config = EsploraBlockchainConfig { + base_url: url.into(), + proxy: Some(socks_url.into()), + concurrency: None, + stop_gap: 20, + timeout: Some(120), + }; + + EsploraBlockchain::from_config(&config).unwrap() +} +``` + +There are two notable differences though. First, we call `build_async()` to +create an asynchronous Esplora client. Second the SOCKS5 URL scheme is +“socks5h”. It’s not a typo. The async client supports two SOCKS5 schemes +“socks5” and “socks5h”. The difference between them that the former +makes the client to resolve domain names, and the latter does not, so the +client passes them to the proxy as is. A regular DNS resolver cannot +resolve Tor onion addresses, so we should use “socks5h” here. + +## CompactFiltersBlockchain + +Add these lines to the dependencies section of `Cargo.toml` file to enable +BIP-157/BIP-158 compact filter support. + +It can take a while to sync a wallet using compact filters over Tor, so be +patient. + +```toml +[dependencies] +bdk = { version = "^0.26", default-features = false, features = ["compact_filters"]} +``` + +Now add the required imports into `src/main.rs`. + +```rust +use std::sync::Arc; +use bdk::blockchain::compact_filters::{Mempool, Peer}; +use bdk::blockchain::{CompactFiltersBlockchain, GetHeight}; +``` + +`create_blockchain()` function will look like this. + +```rust +fn create_blockchain() -> CompactFiltersBlockchain { + let peer_addr = "neutrino.testnet3.suredbits.com:18333"; + let socks_addr = "127.0.0.1:9050"; + + let mempool = Arc::new(Mempool::default()); + + println!("Connecting to {} via {}", peer_addr, socks_addr); + + let peer = + Peer::connect_proxy(peer_addr, socks_addr, None, mempool, Network::Testnet).unwrap(); + + CompactFiltersBlockchain::new(vec![peer], "./wallet-filters", Some(500_000)).unwrap() +} +``` + +Here we use `Peer::connect_proxy()` which accepts the address to the SOCKS5 +proxy and performs all the heavy lifting for us. + +## Integrated Tor daemon + +As an application developer you don’t have to rely on your users to install +and start Tor to use your application. Using `libtor` crate you can bundle +Tor daemon with your app. + +`libtor` builds a Tor binary from the source files. Since Tor is written in C +you'll need a C compiler and build tools. + +Install these packages on Mac OS X: + +```bash +xcode-select --install +brew install autoconf +brew install automake +brew install libtool +brew install openssl +brew install pkg-config +export LDFLAGS="-L/opt/homebrew/opt/openssl/lib" +export CPPFLAGS="-I/opt/homebrew/opt/openssl/include" +``` + +Or these packages on Ubuntu or another Debian-based Linux distribution: + +```bash +sudo apt install autoconf automake clang file libtool openssl pkg-config +``` + +Then add these dependencies to the `Cargo.toml` file. + +```toml +[dependencies] +bdk = { version = "^0.26" } +libtor = "47.8.0+0.4.7.x" +``` + +This is an example of how we can use `libtor` to start a Tor daemon. + +```rust +use std::fs::File; +use std::io::prelude::*; +use std::thread; +use std::time::Duration; + +use libtor::LogDestination; +use libtor::LogLevel; +use libtor::{HiddenServiceVersion, Tor, TorAddress, TorFlag}; + +use std::env; + +pub fn start_tor() -> String { + let socks_port = 19050; + + let data_dir = format!("{}/{}", env::temp_dir().display(), "bdk-tor"); + let log_file_name = format!("{}/{}", &data_dir, "log"); + + println!("Staring Tor in {}", &data_dir); + + truncate_log(&log_file_name); + + Tor::new() + .flag(TorFlag::DataDirectory(data_dir.into())) + .flag(TorFlag::LogTo( + LogLevel::Notice, + LogDestination::File(log_file_name.as_str().into()), + )) + .flag(TorFlag::SocksPort(socks_port)) + .flag(TorFlag::Custom("ExitPolicy reject *:*".into())) + .flag(TorFlag::Custom("BridgeRelay 0".into())) + .start_background(); + + let mut started = false; + let mut tries = 0; + while !started { + tries += 1; + if tries > 120 { + panic!( + "It took too long to start Tor. See {} for details", + &log_file_name + ); + } + + thread::sleep(Duration::from_millis(1000)); + started = find_string_in_log(&log_file_name, &"Bootstrapped 100%".into()); + } + + println!("Tor started"); + + format!("127.0.0.1:{}", socks_port) +} +``` + +First, we create a Tor object, and then we call `start_background()` method +to start it in the background. After that, we continuously try to find +“Bootstrapped 100%” string in the log file. Once we find it, Tor is +ready to proxy our connections. We use port 19050 because, the user can +have their own instance of Tor running already. + +Next you can modify `create_blockchain()` like this + +```rust +fn create_blockchain() -> ElectrumBlockchain { + let url = "ssl://electrum.blockstream.info:60002"; + let socks_addr = start_tor(); + + ... +} +``` + +In this example we start Tor first, then use the address returned by +`start_tor()` function as proxy address. + +We omitted `find_string_in_log()` and `truncate_log()` for brevity. You +can find their implementations in [esplora_backend_with_tor.rs](https://github.com/bitcoindevkit/bdk/blob/master/examples/esplora_backend_with_tor.rs)