From 8811a0a205eadf21653454c2e1908afe4efbe75a Mon Sep 17 00:00:00 2001 From: Philipp Hoenisch Date: Mon, 26 Oct 2020 16:55:53 +1100 Subject: [PATCH 1/7] Add Tor to main --- swap/src/alice.rs | 52 +++++++++++++++++++++++-- swap/src/bob.rs | 11 +++++- swap/src/cli.rs | 5 +++ swap/src/main.rs | 72 +++++++++++++++++++++++++++++++---- swap/src/network/transport.rs | 45 ++++++++++++++++++++-- 5 files changed, 168 insertions(+), 17 deletions(-) diff --git a/swap/src/alice.rs b/swap/src/alice.rs index b0553bd3..72a05b76 100644 --- a/swap/src/alice.rs +++ b/swap/src/alice.rs @@ -28,20 +28,38 @@ use xmr_btc::{alice::State0, bob, monero}; pub type Swarm = libp2p::Swarm; +#[cfg(not(feature = "tor"))] +pub async fn swap( + listen: Multiaddr, + redeem_address: ::bitcoin::Address, + punish_address: ::bitcoin::Address, +) -> Result<()> { + let swarm = new_swarm(listen)?; + _swap(redeem_address, punish_address, swarm).await +} +#[cfg(feature = "tor")] +pub async fn swap( + listen: Multiaddr, + local_port: u16, + redeem_address: ::bitcoin::Address, + punish_address: ::bitcoin::Address, +) -> Result<()> { + let swarm = new_swarm(listen, local_port)?; + _swap(redeem_address, punish_address, swarm).await +} + // TODO: After we have done some testing replace all the 'panic's with log // statements or error returns. // FIXME: This whole function is horrible, needs total re-write. -pub async fn swap( - listen: Multiaddr, +async fn _swap( redeem_address: ::bitcoin::Address, punish_address: ::bitcoin::Address, + mut swarm: Swarm, ) -> Result<()> { let message0: bob::Message0; let mut last_amounts: Option = None; - let mut swarm = new_swarm(listen)?; - loop { match swarm.next().await { OutEvent::ConnectionEstablished(id) => { @@ -111,6 +129,32 @@ pub async fn swap( Ok(()) } +#[cfg(feature = "tor")] +fn new_swarm(listen: Multiaddr, port: u16) -> Result { + use anyhow::Context as _; + + let behaviour = Alice::default(); + + let local_key_pair = behaviour.identity(); + let local_peer_id = behaviour.peer_id(); + + let transport = transport::build(local_key_pair, Some((listen.clone(), port)))?; + + let mut swarm = libp2p::swarm::SwarmBuilder::new(transport, behaviour, local_peer_id.clone()) + .executor(Box::new(TokioExecutor { + handle: tokio::runtime::Handle::current(), + })) + .build(); + + Swarm::listen_on(&mut swarm, listen.clone()) + .with_context(|| format!("Address is not supported: {:#}", listen))?; + + tracing::info!("Initialized swarm: {}", local_peer_id); + + Ok(swarm) +} + +#[cfg(not(feature = "tor"))] fn new_swarm(listen: Multiaddr) -> Result { use anyhow::Context as _; diff --git a/swap/src/bob.rs b/swap/src/bob.rs index a72380c9..a955d0fb 100644 --- a/swap/src/bob.rs +++ b/swap/src/bob.rs @@ -114,7 +114,16 @@ fn new_swarm() -> Result { let local_key_pair = behaviour.identity(); let local_peer_id = behaviour.peer_id(); - let transport = transport::build(local_key_pair)?; + let transport = { + #[cfg(feature = "tor")] + { + transport::build(local_key_pair, None)? + } + #[cfg(not(feature = "tor"))] + { + transport::build(local_key_pair)? + } + }; let swarm = libp2p::swarm::SwarmBuilder::new(transport, behaviour, local_peer_id.clone()) .executor(Box::new(TokioExecutor { diff --git a/swap/src/cli.rs b/swap/src/cli.rs index fd4f9a59..5504d87b 100644 --- a/swap/src/cli.rs +++ b/swap/src/cli.rs @@ -11,4 +11,9 @@ pub struct Options { /// Run the swap as Bob and try to swap this many BTC (in satoshi). #[structopt(long = "sats")] pub satoshis: Option, + + /// Alice's onion multitaddr (only required for Bob, Alice will autogenerate + /// one) + #[structopt(long)] + pub alice_address: Option, } diff --git a/swap/src/main.rs b/swap/src/main.rs index b405f6de..76a05526 100644 --- a/swap/src/main.rs +++ b/swap/src/main.rs @@ -12,22 +12,21 @@ )] #![forbid(unsafe_code)] -use anyhow::{bail, Result}; +use anyhow::{bail, Context, Result}; +use cli::Options; use futures::{channel::mpsc, StreamExt}; use libp2p::Multiaddr; use log::LevelFilter; use std::{io, io::Write, process}; use structopt::StructOpt; +use swap::{alice, bitcoin::Wallet, bob, Cmd, Rsp, SwapAmounts}; use tracing::info; use url::Url; +use xmr_btc::bitcoin::{BroadcastSignedTransaction, BuildTxLockPsbt, SignTxLock}; mod cli; mod trace; -use cli::Options; -use swap::{alice, bitcoin::Wallet, bob, Cmd, Rsp, SwapAmounts}; -use xmr_btc::bitcoin::{BroadcastSignedTransaction, BuildTxLockPsbt, SignTxLock}; - // TODO: Add root seed file instead of generating new seed each run. // TODO: Remove all instances of the todo! macro @@ -35,7 +34,14 @@ use xmr_btc::bitcoin::{BroadcastSignedTransaction, BuildTxLockPsbt, SignTxLock}; // Alice's address and port until we have a config file. pub const PORT: u16 = 9876; // Arbitrarily chosen. pub const ADDR: &str = "127.0.0.1"; -pub const BITCOIND_JSON_RPC_URL: &str = "127.0.0.1:8332"; +pub const BITCOIND_JSON_RPC_URL: &str = "http://127.0.0.1:8332"; + +#[cfg(feature = "tor")] +use swap::tor::{AuthenticatedConnection, UnauthenticatedConnection}; +#[cfg(feature = "tor")] +use torut::onion::TorSecretKeyV3; +#[cfg(feature = "tor")] +pub const TOR_PORT: u16 = PORT + 1; #[tokio::main] async fn main() -> Result<()> { @@ -43,7 +49,21 @@ async fn main() -> Result<()> { trace::init_tracing(LevelFilter::Debug)?; + #[cfg(feature = "tor")] + let (addr, _ac) = { + let tor_secret_key = TorSecretKeyV3::generate(); + let onion_address = tor_secret_key + .public() + .get_onion_address() + .get_address_without_dot_onion(); + ( + format!("/onion3/{}:{}", onion_address, TOR_PORT), + create_tor_service(tor_secret_key).await?, + ) + }; + #[cfg(not(feature = "tor"))] let addr = format!("/ip4/{}/tcp/{}", ADDR, PORT); + let alice: Multiaddr = addr.parse().expect("failed to parse Alice's address"); if opt.as_alice { @@ -71,6 +91,12 @@ async fn main() -> Result<()> { } else { info!("running swap node as Bob ..."); + let alice_address = match opt.alice_address { + Some(addr) => addr, + None => bail!("Address required to dial"), + }; + let alice_address = multiaddr(&alice_address)?; + let url = Url::parse(BITCOIND_JSON_RPC_URL).expect("failed to parse url"); let bitcoin_wallet = Wallet::new("bob", &url) .await @@ -86,7 +112,7 @@ async fn main() -> Result<()> { (None, None) => bail!("Please supply an amount to swap"), (Some(_picos), _) => todo!("support starting with picos"), (None, Some(sats)) => { - swap_as_bob(sats, alice, refund, bitcoin_wallet).await?; + swap_as_bob(sats, alice_address, refund, bitcoin_wallet).await?; } }; } @@ -94,12 +120,35 @@ async fn main() -> Result<()> { Ok(()) } +#[cfg(feature = "tor")] +async fn create_tor_service(tor_secret_key: TorSecretKeyV3) -> Result { + // todo use configurable ports for tor connection + let mut authenticated_connection = UnauthenticatedConnection::default() + .init_authenticated_connection() + .await?; + tracing::info!("Tor authenticated."); + + authenticated_connection + .add_service(TOR_PORT, &tor_secret_key) + .await?; + tracing::info!("Tor service added."); + + Ok(authenticated_connection) +} + async fn swap_as_alice( addr: Multiaddr, redeem: bitcoin::Address, punish: bitcoin::Address, ) -> Result<()> { - alice::swap(addr, redeem, punish).await + #[cfg(not(feature = "tor"))] + { + alice::swap(addr, redeem, punish).await + } + #[cfg(feature = "tor")] + { + alice::swap(addr, PORT, redeem, punish).await + } } async fn swap_as_bob( @@ -164,3 +213,10 @@ fn verify(amounts: SwapAmounts) -> Rsp { fn is_yes(s: &str) -> bool { matches!(s, "y" | "Y" | "yes" | "YES" | "Yes") } + +fn multiaddr(s: &str) -> Result { + let addr = s + .parse() + .with_context(|| format!("failed to parse multiaddr: {}", s))?; + Ok(addr) +} diff --git a/swap/src/network/transport.rs b/swap/src/network/transport.rs index 19da6c8f..19ce5021 100644 --- a/swap/src/network/transport.rs +++ b/swap/src/network/transport.rs @@ -10,18 +10,18 @@ use libp2p::{ dns::DnsConfig, mplex::MplexConfig, noise::{self, NoiseConfig, X25519Spec}, - tcp::TokioTcpConfig, yamux, PeerId, }; -// TOOD: Add the tor transport builder. - -/// Builds a libp2p transport with the following features: +/// Builds a libp2p transport without Tor with the following features: /// - TcpConnection /// - DNS name resolution /// - authentication via noise /// - multiplexing via yamux or mplex +#[cfg(not(feature = "tor"))] pub fn build(id_keys: identity::Keypair) -> Result { + use libp2p::tcp::TokioTcpConfig; + let dh_keys = noise::Keypair::::new().into_authentic(&id_keys)?; let noise = NoiseConfig::xx(dh_keys).into_authenticated(); @@ -41,4 +41,41 @@ pub fn build(id_keys: identity::Keypair) -> Result { Ok(transport) } +/// Builds a libp2p transport with Tor and with the following features: +/// - TCP connection over the Tor network +/// - DNS name resolution +/// - authentication via noise +/// - multiplexing via yamux or mplex +#[cfg(feature = "tor")] +pub fn build( + id_keys: identity::Keypair, + address_port_pair: Option<(libp2p::core::Multiaddr, u16)>, +) -> Result { + use libp2p_tokio_socks5::Socks5TokioTcpConfig; + use std::collections::HashMap; + + let mut map = HashMap::new(); + if let Some((addr, port)) = address_port_pair { + map.insert(addr, port); + } + + let dh_keys = noise::Keypair::::new().into_authentic(&id_keys)?; + let noise = NoiseConfig::xx(dh_keys).into_authenticated(); + + let socks = Socks5TokioTcpConfig::default().nodelay(true).onion_map(map); + let dns = DnsConfig::new(socks)?; + + let transport = dns + .upgrade(Version::V1) + .authenticate(noise) + .multiplex(SelectUpgrade::new( + yamux::Config::default(), + MplexConfig::new(), + )) + .map(|(peer, muxer), _| (peer, StreamMuxerBox::new(muxer))) + .boxed(); + + Ok(transport) +} + pub type SwapTransport = Boxed<(PeerId, StreamMuxerBox)>; From 7f3aa644a02c5a55a666e4a5a4b8214c90fba53f Mon Sep 17 00:00:00 2001 From: Philipp Hoenisch Date: Tue, 27 Oct 2020 16:02:32 +1100 Subject: [PATCH 2/7] Extre CI flag to run clippy for wihtout all features enabled --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9477b09c..d837e11a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,6 +41,9 @@ jobs: run: cargo fmt --all -- --check - name: Run clippy + run: cargo clippy --workspace --all-targets -- -D warnings + + - name: Run clippy all features run: cargo clippy --workspace --all-targets --all-features -- -D warnings build_test: From 5e3590410168c473f7fccc26f299e21d44c57bbe Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Wed, 28 Oct 2020 08:38:10 +1100 Subject: [PATCH 3/7] Use an option for the local port We can wrap the local port in an option and pass None when we are not using Tor. This reduces code duplication. --- swap/src/alice.rs | 69 +++++++++++------------------------------------ swap/src/main.rs | 4 +-- 2 files changed, 18 insertions(+), 55 deletions(-) diff --git a/swap/src/alice.rs b/swap/src/alice.rs index 72a05b76..bbaf00ac 100644 --- a/swap/src/alice.rs +++ b/swap/src/alice.rs @@ -1,6 +1,6 @@ //! Run an XMR/BTC swap in the role of Alice. //! Alice holds XMR and wishes receive BTC. -use anyhow::Result; +use anyhow::{bail, Result}; use libp2p::{ core::{identity::Keypair, Multiaddr}, request_response::ResponseChannel, @@ -28,35 +28,13 @@ use xmr_btc::{alice::State0, bob, monero}; pub type Swarm = libp2p::Swarm; -#[cfg(not(feature = "tor"))] pub async fn swap( listen: Multiaddr, + local_port: Option, redeem_address: ::bitcoin::Address, punish_address: ::bitcoin::Address, ) -> Result<()> { - let swarm = new_swarm(listen)?; - _swap(redeem_address, punish_address, swarm).await -} -#[cfg(feature = "tor")] -pub async fn swap( - listen: Multiaddr, - local_port: u16, - redeem_address: ::bitcoin::Address, - punish_address: ::bitcoin::Address, -) -> Result<()> { - let swarm = new_swarm(listen, local_port)?; - _swap(redeem_address, punish_address, swarm).await -} - -// TODO: After we have done some testing replace all the 'panic's with log -// statements or error returns. - -// FIXME: This whole function is horrible, needs total re-write. -async fn _swap( - redeem_address: ::bitcoin::Address, - punish_address: ::bitcoin::Address, - mut swarm: Swarm, -) -> Result<()> { + let mut swarm = new_swarm(listen, local_port)?; let message0: bob::Message0; let mut last_amounts: Option = None; @@ -129,8 +107,7 @@ async fn _swap( Ok(()) } -#[cfg(feature = "tor")] -fn new_swarm(listen: Multiaddr, port: u16) -> Result { +fn new_swarm(listen: Multiaddr, port: Option) -> Result { use anyhow::Context as _; let behaviour = Alice::default(); @@ -138,7 +115,18 @@ fn new_swarm(listen: Multiaddr, port: u16) -> Result { let local_key_pair = behaviour.identity(); let local_peer_id = behaviour.peer_id(); - let transport = transport::build(local_key_pair, Some((listen.clone(), port)))?; + let transport; + #[cfg(feature = "tor")] + { + transport = match port { + Some(port) => transport::build(local_key_pair, Some((listen.clone(), port)))?, + None => bail!("Must supply local port"), + }; + } + #[cfg(not(feature = "tor"))] + { + transport = transport::build(local_key_pair)?; + } let mut swarm = libp2p::swarm::SwarmBuilder::new(transport, behaviour, local_peer_id.clone()) .executor(Box::new(TokioExecutor { @@ -154,31 +142,6 @@ fn new_swarm(listen: Multiaddr, port: u16) -> Result { Ok(swarm) } -#[cfg(not(feature = "tor"))] -fn new_swarm(listen: Multiaddr) -> Result { - use anyhow::Context as _; - - let behaviour = Alice::default(); - - let local_key_pair = behaviour.identity(); - let local_peer_id = behaviour.peer_id(); - - let transport = transport::build(local_key_pair)?; - - let mut swarm = libp2p::swarm::SwarmBuilder::new(transport, behaviour, local_peer_id.clone()) - .executor(Box::new(TokioExecutor { - handle: tokio::runtime::Handle::current(), - })) - .build(); - - Swarm::listen_on(&mut swarm, listen.clone()) - .with_context(|| format!("Address is not supported: {:#}", listen))?; - - info!("Initialized swarm: {}", local_peer_id); - - Ok(swarm) -} - #[allow(clippy::large_enum_variant)] #[derive(Debug)] pub enum OutEvent { diff --git a/swap/src/main.rs b/swap/src/main.rs index 76a05526..a6d830e6 100644 --- a/swap/src/main.rs +++ b/swap/src/main.rs @@ -143,11 +143,11 @@ async fn swap_as_alice( ) -> Result<()> { #[cfg(not(feature = "tor"))] { - alice::swap(addr, redeem, punish).await + alice::swap(addr, None, redeem, punish).await } #[cfg(feature = "tor")] { - alice::swap(addr, PORT, redeem, punish).await + alice::swap(addr, Some(PORT), redeem, punish).await } } From 42d194f7588a257e4b2c16d2b99c83d8e01b4809 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Wed, 28 Oct 2020 09:21:37 +1100 Subject: [PATCH 4/7] Use fully qualified path for bail --- swap/src/alice.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/swap/src/alice.rs b/swap/src/alice.rs index bbaf00ac..0323f308 100644 --- a/swap/src/alice.rs +++ b/swap/src/alice.rs @@ -1,6 +1,6 @@ //! Run an XMR/BTC swap in the role of Alice. //! Alice holds XMR and wishes receive BTC. -use anyhow::{bail, Result}; +use anyhow::Result; use libp2p::{ core::{identity::Keypair, Multiaddr}, request_response::ResponseChannel, @@ -120,7 +120,7 @@ fn new_swarm(listen: Multiaddr, port: Option) -> Result { { transport = match port { Some(port) => transport::build(local_key_pair, Some((listen.clone(), port)))?, - None => bail!("Must supply local port"), + None => anyhow::bail!("Must supply local port"), }; } #[cfg(not(feature = "tor"))] From 464b69942691978bd1e74e32321e6f01b088364a Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Wed, 28 Oct 2020 09:45:39 +1100 Subject: [PATCH 5/7] Fail if user passes in local port for non-tor usage Local port is only used when running behind tor. Fail if user passes a local port number when running in non-tor mode. --- swap/src/alice.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/swap/src/alice.rs b/swap/src/alice.rs index 0323f308..50235851 100644 --- a/swap/src/alice.rs +++ b/swap/src/alice.rs @@ -125,7 +125,10 @@ fn new_swarm(listen: Multiaddr, port: Option) -> Result { } #[cfg(not(feature = "tor"))] { - transport = transport::build(local_key_pair)?; + transport = match port { + None => transport::build(local_key_pair)?, + Some(port) => anyhow::bail!("local port should not be provided for non-tor usage"), + }; } let mut swarm = libp2p::swarm::SwarmBuilder::new(transport, behaviour, local_peer_id.clone()) From a1351e5461d51537adc92c420473aa713c1c4b2a Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Wed, 28 Oct 2020 10:11:04 +1100 Subject: [PATCH 6/7] Do not declare 'port' variable --- swap/src/alice.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/swap/src/alice.rs b/swap/src/alice.rs index 50235851..ca8a45e3 100644 --- a/swap/src/alice.rs +++ b/swap/src/alice.rs @@ -127,7 +127,7 @@ fn new_swarm(listen: Multiaddr, port: Option) -> Result { { transport = match port { None => transport::build(local_key_pair)?, - Some(port) => anyhow::bail!("local port should not be provided for non-tor usage"), + Some(_) => anyhow::bail!("local port should not be provided for non-tor usage"), }; } From b8fd9a734ff611f5a2c482d0526c70bccff71cbf Mon Sep 17 00:00:00 2001 From: Philipp Hoenisch Date: Wed, 28 Oct 2020 11:18:14 +1100 Subject: [PATCH 7/7] Use full path imports for dependencies to reduce cfg hell --- .github/workflows/ci.yml | 4 ++-- swap/src/main.rs | 14 ++++++-------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d837e11a..cd8a6c30 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,10 +40,10 @@ jobs: - name: Check code formatting run: cargo fmt --all -- --check - - name: Run clippy + - name: Run clippy with default features run: cargo clippy --workspace --all-targets -- -D warnings - - name: Run clippy all features + - name: Run clippy with all features enabled (e.g. tor) run: cargo clippy --workspace --all-targets --all-features -- -D warnings build_test: diff --git a/swap/src/main.rs b/swap/src/main.rs index a6d830e6..e981dccd 100644 --- a/swap/src/main.rs +++ b/swap/src/main.rs @@ -36,10 +36,6 @@ pub const PORT: u16 = 9876; // Arbitrarily chosen. pub const ADDR: &str = "127.0.0.1"; pub const BITCOIND_JSON_RPC_URL: &str = "http://127.0.0.1:8332"; -#[cfg(feature = "tor")] -use swap::tor::{AuthenticatedConnection, UnauthenticatedConnection}; -#[cfg(feature = "tor")] -use torut::onion::TorSecretKeyV3; #[cfg(feature = "tor")] pub const TOR_PORT: u16 = PORT + 1; @@ -51,7 +47,7 @@ async fn main() -> Result<()> { #[cfg(feature = "tor")] let (addr, _ac) = { - let tor_secret_key = TorSecretKeyV3::generate(); + let tor_secret_key = torut::onion::TorSecretKeyV3::generate(); let onion_address = tor_secret_key .public() .get_onion_address() @@ -121,9 +117,11 @@ async fn main() -> Result<()> { } #[cfg(feature = "tor")] -async fn create_tor_service(tor_secret_key: TorSecretKeyV3) -> Result { - // todo use configurable ports for tor connection - let mut authenticated_connection = UnauthenticatedConnection::default() +async fn create_tor_service( + tor_secret_key: torut::onion::TorSecretKeyV3, +) -> Result { + // TODO use configurable ports for tor connection + let mut authenticated_connection = swap::tor::UnauthenticatedConnection::default() .init_authenticated_connection() .await?; tracing::info!("Tor authenticated.");