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; -// 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. +#[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. +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)>;