]> Untitled Git - bitcoindevkit.org/commitdiff
Add tutorial on using BDK with Tor
authorrorp <rorp@users.noreply.github.com>
Sat, 17 Dec 2022 20:33:23 +0000 (12:33 -0800)
committerthunderbiscuit <thunderbiscuit@protonmail.com>
Mon, 28 Aug 2023 12:52:10 +0000 (08:52 -0400)
docs/.vuepress/config.js
docs/tutorials/bdk_with_tor.md [new file with mode: 0644]

index dbff91d14e4af11eb892a29d6329878c26384991..1c9667b38a28fe8a6bd3631146aa3a131e0e0ae0 100644 (file)
@@ -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 (file)
index 0000000..438db22
--- /dev/null
@@ -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<MemoryDatabase> {
+    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)