2020-10-15 22:14:39 +00:00
|
|
|
#![warn(
|
|
|
|
unused_extern_crates,
|
|
|
|
missing_debug_implementations,
|
|
|
|
missing_copy_implementations,
|
|
|
|
rust_2018_idioms,
|
|
|
|
clippy::cast_possible_truncation,
|
|
|
|
clippy::cast_sign_loss,
|
|
|
|
clippy::fallible_impl_from,
|
|
|
|
clippy::cast_precision_loss,
|
|
|
|
clippy::cast_possible_wrap,
|
|
|
|
clippy::dbg_macro
|
|
|
|
)]
|
|
|
|
#![forbid(unsafe_code)]
|
|
|
|
|
2020-10-29 11:18:59 +00:00
|
|
|
use anyhow::Result;
|
2020-10-15 22:14:39 +00:00
|
|
|
use futures::{channel::mpsc, StreamExt};
|
2020-11-03 00:05:39 +00:00
|
|
|
use libp2p::Multiaddr;
|
2020-11-05 05:55:30 +00:00
|
|
|
use prettytable::{row, Table};
|
2020-12-10 00:18:45 +00:00
|
|
|
use rand::rngs::OsRng;
|
2020-10-27 01:11:03 +00:00
|
|
|
use std::{io, io::Write, process, sync::Arc};
|
2020-10-15 22:14:39 +00:00
|
|
|
use structopt::StructOpt;
|
2020-10-29 11:18:59 +00:00
|
|
|
use swap::{
|
2020-11-30 02:25:11 +00:00
|
|
|
alice, bitcoin, bob,
|
2020-11-12 00:06:34 +00:00
|
|
|
cli::Options,
|
2020-11-03 05:08:46 +00:00
|
|
|
monero,
|
2020-10-29 11:18:59 +00:00
|
|
|
network::transport::{build, build_tor, SwapTransport},
|
2020-11-06 00:10:22 +00:00
|
|
|
recover::recover,
|
2020-11-12 00:06:34 +00:00
|
|
|
storage::Database,
|
2020-12-10 00:18:45 +00:00
|
|
|
Cmd, Rsp, SwapAmounts, PUNISH_TIMELOCK, REFUND_TIMELOCK,
|
2020-10-29 11:18:59 +00:00
|
|
|
};
|
2020-10-15 22:14:39 +00:00
|
|
|
use tracing::info;
|
2020-12-10 00:18:45 +00:00
|
|
|
use xmr_btc::{alice::State0, cross_curve_dleq};
|
2020-10-21 22:58:22 +00:00
|
|
|
|
2020-11-05 05:55:30 +00:00
|
|
|
#[macro_use]
|
|
|
|
extern crate prettytable;
|
|
|
|
|
2020-10-15 22:14:39 +00:00
|
|
|
// TODO: Add root seed file instead of generating new seed each run.
|
|
|
|
|
|
|
|
#[tokio::main]
|
|
|
|
async fn main() -> Result<()> {
|
2020-11-03 03:33:48 +00:00
|
|
|
let opt = Options::from_args();
|
2020-10-15 22:14:39 +00:00
|
|
|
|
2020-11-05 05:55:30 +00:00
|
|
|
// This currently creates the directory if it's not there in the first place
|
|
|
|
let db = Database::open(std::path::Path::new("./.swap-db/")).unwrap();
|
2020-11-03 06:08:31 +00:00
|
|
|
|
2020-10-29 03:45:45 +00:00
|
|
|
match opt {
|
|
|
|
Options::Alice {
|
2020-11-03 05:08:46 +00:00
|
|
|
bitcoind_url,
|
|
|
|
monerod_url,
|
2020-10-29 03:45:45 +00:00
|
|
|
listen_addr,
|
2020-10-29 11:18:59 +00:00
|
|
|
tor_port,
|
2020-10-29 03:45:45 +00:00
|
|
|
} => {
|
|
|
|
info!("running swap node as Alice ...");
|
|
|
|
|
2020-12-10 00:18:45 +00:00
|
|
|
let bitcoin_wallet = bitcoin::Wallet::new("alice", bitcoind_url)
|
|
|
|
.await
|
|
|
|
.expect("failed to create bitcoin wallet");
|
|
|
|
let bitcoin_wallet = Arc::new(bitcoin_wallet);
|
|
|
|
|
|
|
|
let monero_wallet = Arc::new(monero::Wallet::new(monerod_url));
|
|
|
|
|
|
|
|
let rng = &mut OsRng;
|
|
|
|
let a = bitcoin::SecretKey::new_random(rng);
|
|
|
|
let s_a = cross_curve_dleq::Scalar::random(rng);
|
|
|
|
let v_a = xmr_btc::monero::PrivateViewKey::new_random(rng);
|
|
|
|
let redeem_address = bitcoin_wallet.as_ref().new_address().await?;
|
|
|
|
let punish_address = redeem_address.clone();
|
|
|
|
let state0 = State0::new(
|
|
|
|
a,
|
|
|
|
s_a,
|
|
|
|
v_a,
|
|
|
|
// todo: get from CLI args
|
|
|
|
bitcoin::Amount::from_sat(100),
|
|
|
|
// todo: get from CLI args
|
|
|
|
monero::Amount::from_piconero(1000000),
|
|
|
|
REFUND_TIMELOCK,
|
|
|
|
PUNISH_TIMELOCK,
|
|
|
|
redeem_address,
|
|
|
|
punish_address,
|
|
|
|
);
|
|
|
|
|
|
|
|
let behaviour = alice::Behaviour::new(state0);
|
2020-10-29 11:18:59 +00:00
|
|
|
let local_key_pair = behaviour.identity();
|
|
|
|
|
2020-11-03 00:00:39 +00:00
|
|
|
let (listen_addr, _ac, transport) = match tor_port {
|
2020-10-29 11:18:59 +00:00
|
|
|
Some(tor_port) => {
|
|
|
|
let tor_secret_key = torut::onion::TorSecretKeyV3::generate();
|
|
|
|
let onion_address = tor_secret_key
|
|
|
|
.public()
|
|
|
|
.get_onion_address()
|
|
|
|
.get_address_without_dot_onion();
|
|
|
|
let onion_address_string = format!("/onion3/{}:{}", onion_address, tor_port);
|
|
|
|
let addr: Multiaddr = onion_address_string.parse()?;
|
|
|
|
let ac = create_tor_service(tor_secret_key, tor_port).await?;
|
2020-11-03 03:54:36 +00:00
|
|
|
let transport = build_tor(local_key_pair, Some((addr.clone(), tor_port)))?;
|
2020-10-29 11:18:59 +00:00
|
|
|
(addr, Some(ac), transport)
|
|
|
|
}
|
|
|
|
None => {
|
|
|
|
let transport = build(local_key_pair)?;
|
|
|
|
(listen_addr, None, transport)
|
|
|
|
}
|
2020-10-29 03:45:45 +00:00
|
|
|
};
|
|
|
|
|
2020-11-03 06:08:31 +00:00
|
|
|
swap_as_alice(
|
|
|
|
bitcoin_wallet,
|
|
|
|
monero_wallet,
|
|
|
|
db,
|
|
|
|
listen_addr,
|
2020-11-03 05:08:46 +00:00
|
|
|
transport,
|
|
|
|
behaviour,
|
|
|
|
)
|
|
|
|
.await?;
|
2020-10-29 03:45:45 +00:00
|
|
|
}
|
|
|
|
Options::Bob {
|
2020-10-29 11:18:59 +00:00
|
|
|
alice_addr,
|
2020-10-29 03:45:45 +00:00
|
|
|
satoshis,
|
2020-11-03 05:08:46 +00:00
|
|
|
bitcoind_url,
|
|
|
|
monerod_url,
|
2020-11-03 03:54:36 +00:00
|
|
|
tor,
|
2020-10-29 03:45:45 +00:00
|
|
|
} => {
|
|
|
|
info!("running swap node as Bob ...");
|
2020-10-26 05:55:53 +00:00
|
|
|
|
2020-11-30 02:25:11 +00:00
|
|
|
let behaviour = bob::Behaviour::default();
|
2020-10-29 11:18:59 +00:00
|
|
|
let local_key_pair = behaviour.identity();
|
|
|
|
|
2020-11-03 03:54:36 +00:00
|
|
|
let transport = match tor {
|
|
|
|
true => build_tor(local_key_pair, None)?,
|
|
|
|
false => build(local_key_pair)?,
|
|
|
|
};
|
2020-10-29 11:18:59 +00:00
|
|
|
|
2020-11-03 05:08:46 +00:00
|
|
|
let bitcoin_wallet = bitcoin::Wallet::new("bob", bitcoind_url)
|
2020-10-29 03:45:45 +00:00
|
|
|
.await
|
|
|
|
.expect("failed to create bitcoin wallet");
|
2020-11-03 05:08:46 +00:00
|
|
|
let bitcoin_wallet = Arc::new(bitcoin_wallet);
|
2020-10-15 22:14:39 +00:00
|
|
|
|
2020-11-03 05:08:46 +00:00
|
|
|
let monero_wallet = Arc::new(monero::Wallet::new(monerod_url));
|
2020-10-15 22:14:39 +00:00
|
|
|
|
2020-10-29 11:18:59 +00:00
|
|
|
swap_as_bob(
|
2020-11-03 05:08:46 +00:00
|
|
|
bitcoin_wallet,
|
|
|
|
monero_wallet,
|
2020-11-03 06:08:31 +00:00
|
|
|
db,
|
2020-10-29 11:18:59 +00:00
|
|
|
satoshis,
|
|
|
|
alice_addr,
|
|
|
|
transport,
|
|
|
|
behaviour,
|
|
|
|
)
|
|
|
|
.await?;
|
2020-10-15 22:14:39 +00:00
|
|
|
}
|
2020-11-05 05:55:30 +00:00
|
|
|
Options::History => {
|
|
|
|
let mut table = Table::new();
|
|
|
|
|
|
|
|
table.add_row(row!["SWAP ID", "STATE"]);
|
|
|
|
|
|
|
|
for (swap_id, state) in db.all()? {
|
|
|
|
table.add_row(row![swap_id, state]);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Print the table to stdout
|
|
|
|
table.printstd();
|
|
|
|
}
|
2020-11-06 00:10:22 +00:00
|
|
|
Options::Recover {
|
|
|
|
swap_id,
|
|
|
|
bitcoind_url,
|
|
|
|
monerod_url,
|
|
|
|
} => {
|
|
|
|
let state = db.get_state(swap_id)?;
|
|
|
|
let bitcoin_wallet = bitcoin::Wallet::new("bob", bitcoind_url)
|
|
|
|
.await
|
|
|
|
.expect("failed to create bitcoin wallet");
|
|
|
|
let monero_wallet = monero::Wallet::new(monerod_url);
|
|
|
|
|
|
|
|
recover(bitcoin_wallet, monero_wallet, state).await?;
|
|
|
|
}
|
2020-10-15 22:14:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-10-28 00:18:14 +00:00
|
|
|
async fn create_tor_service(
|
|
|
|
tor_secret_key: torut::onion::TorSecretKeyV3,
|
2020-10-29 11:18:59 +00:00
|
|
|
tor_port: u16,
|
2020-10-28 00:18:14 +00:00
|
|
|
) -> Result<swap::tor::AuthenticatedConnection> {
|
|
|
|
// TODO use configurable ports for tor connection
|
|
|
|
let mut authenticated_connection = swap::tor::UnauthenticatedConnection::default()
|
2020-10-26 05:55:53 +00:00
|
|
|
.init_authenticated_connection()
|
|
|
|
.await?;
|
|
|
|
tracing::info!("Tor authenticated.");
|
|
|
|
|
|
|
|
authenticated_connection
|
2020-10-29 11:18:59 +00:00
|
|
|
.add_service(tor_port, &tor_secret_key)
|
2020-10-26 05:55:53 +00:00
|
|
|
.await?;
|
|
|
|
tracing::info!("Tor service added.");
|
|
|
|
|
|
|
|
Ok(authenticated_connection)
|
|
|
|
}
|
|
|
|
|
2020-10-21 03:41:50 +00:00
|
|
|
async fn swap_as_alice(
|
2020-10-27 01:11:03 +00:00
|
|
|
bitcoin_wallet: Arc<swap::bitcoin::Wallet>,
|
|
|
|
monero_wallet: Arc<swap::monero::Wallet>,
|
2020-11-03 04:26:47 +00:00
|
|
|
db: Database,
|
2020-10-21 03:41:50 +00:00
|
|
|
addr: Multiaddr,
|
2020-10-29 11:18:59 +00:00
|
|
|
transport: SwapTransport,
|
2020-11-30 02:25:11 +00:00
|
|
|
behaviour: alice::Behaviour,
|
2020-10-21 03:41:50 +00:00
|
|
|
) -> Result<()> {
|
2020-11-03 06:08:31 +00:00
|
|
|
alice::swap(
|
|
|
|
bitcoin_wallet,
|
|
|
|
monero_wallet,
|
|
|
|
db,
|
|
|
|
addr,
|
|
|
|
transport,
|
|
|
|
behaviour,
|
|
|
|
)
|
|
|
|
.await
|
2020-10-15 22:14:39 +00:00
|
|
|
}
|
|
|
|
|
2020-10-27 02:26:40 +00:00
|
|
|
async fn swap_as_bob(
|
|
|
|
bitcoin_wallet: Arc<swap::bitcoin::Wallet>,
|
|
|
|
monero_wallet: Arc<swap::monero::Wallet>,
|
2020-11-03 04:26:47 +00:00
|
|
|
db: Database,
|
2020-10-21 22:58:22 +00:00
|
|
|
sats: u64,
|
2020-10-22 02:48:30 +00:00
|
|
|
alice: Multiaddr,
|
2020-10-29 11:18:59 +00:00
|
|
|
transport: SwapTransport,
|
2020-11-30 02:25:11 +00:00
|
|
|
behaviour: bob::Behaviour,
|
2020-11-03 05:08:46 +00:00
|
|
|
) -> Result<()> {
|
2020-10-15 22:14:39 +00:00
|
|
|
let (cmd_tx, mut cmd_rx) = mpsc::channel(1);
|
|
|
|
let (mut rsp_tx, rsp_rx) = mpsc::channel(1);
|
2020-10-27 02:26:40 +00:00
|
|
|
tokio::spawn(bob::swap(
|
2020-11-03 05:08:46 +00:00
|
|
|
bitcoin_wallet,
|
|
|
|
monero_wallet,
|
2020-11-02 05:00:10 +00:00
|
|
|
db,
|
2020-11-03 05:08:46 +00:00
|
|
|
sats,
|
|
|
|
alice,
|
|
|
|
cmd_tx,
|
|
|
|
rsp_rx,
|
|
|
|
transport,
|
|
|
|
behaviour,
|
2020-10-27 02:26:40 +00:00
|
|
|
));
|
2020-10-22 02:48:30 +00:00
|
|
|
|
2020-10-15 22:14:39 +00:00
|
|
|
loop {
|
|
|
|
let read = cmd_rx.next().await;
|
|
|
|
match read {
|
|
|
|
Some(cmd) => match cmd {
|
|
|
|
Cmd::VerifyAmounts(p) => {
|
2020-10-15 23:43:32 +00:00
|
|
|
let rsp = verify(p);
|
|
|
|
rsp_tx.try_send(rsp)?;
|
|
|
|
if rsp == Rsp::Abort {
|
|
|
|
process::exit(0);
|
2020-10-15 22:14:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
None => {
|
|
|
|
info!("Channel closed from other end");
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-22 02:30:07 +00:00
|
|
|
fn verify(amounts: SwapAmounts) -> Rsp {
|
2020-10-15 23:43:32 +00:00
|
|
|
let mut s = String::new();
|
|
|
|
println!("Got rate from Alice for XMR/BTC swap\n");
|
2020-10-22 02:30:07 +00:00
|
|
|
println!("{}", amounts);
|
2020-10-15 23:43:32 +00:00
|
|
|
print!("Would you like to continue with this swap [y/N]: ");
|
2020-10-22 02:48:30 +00:00
|
|
|
|
2020-10-15 23:43:32 +00:00
|
|
|
let _ = io::stdout().flush();
|
|
|
|
io::stdin()
|
|
|
|
.read_line(&mut s)
|
|
|
|
.expect("Did not enter a correct string");
|
2020-10-22 02:48:30 +00:00
|
|
|
|
2020-10-15 23:43:32 +00:00
|
|
|
if let Some('\n') = s.chars().next_back() {
|
|
|
|
s.pop();
|
|
|
|
}
|
|
|
|
if let Some('\r') = s.chars().next_back() {
|
|
|
|
s.pop();
|
|
|
|
}
|
2020-10-22 02:48:30 +00:00
|
|
|
|
2020-10-15 23:43:32 +00:00
|
|
|
if !is_yes(&s) {
|
|
|
|
println!("No worries, try again later - Alice updates her rate regularly");
|
|
|
|
return Rsp::Abort;
|
|
|
|
}
|
|
|
|
|
2020-10-22 02:55:50 +00:00
|
|
|
Rsp::VerifiedAmounts
|
2020-10-15 23:43:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn is_yes(s: &str) -> bool {
|
|
|
|
matches!(s, "y" | "Y" | "yes" | "YES" | "Yes")
|
2020-10-15 22:14:39 +00:00
|
|
|
}
|