From b4eeb50c66302c7911026ded7bde6bc5fa22a818 Mon Sep 17 00:00:00 2001 From: Yeastplume Date: Mon, 14 Oct 2019 20:24:09 +0100 Subject: [PATCH] Optional Tor Send/Listen Functionality (#226) * udpate for beta release * initial tor explorations * rustfmt * basic tor tx send working * rustfmt * add tor proxy info to config file * rustfmt * add utilities to output tor hidden service configuration files * output tor config as part of listener startup * rustfmt * fully automate config and startup of tor process * rustfmt * remove unnecessary process kill commands from listener * rustfmt * assume defaults for tor sending config if section doesn't exist in grin-wallet.toml * rustfmt * ignore tor dev test * update default paths output by config, compilation + confirmed working on windows * rustfmt * fix on osx/unix * add timeout to tor connector, remove unwrap in client * allow specifiying tor address without 'http://[].onion' on the command line * fix api test * rustfmt * update address derivation path as per spec * rustfmt * move tor init to separate function * rustfmt * re-ignore tor dev test * listen on tor by default if tor available * rustfmt * test fix * remove explicit send via tor flag, and assume tor if address fits * rustfmt --- Cargo.lock | 289 ++++++++++++-- api/src/owner.rs | 13 +- api/src/owner_rpc_s.rs | 12 +- config/src/comments.rs | 42 ++ config/src/config.rs | 11 +- config/src/lib.rs | 4 +- config/src/types.rs | 22 + controller/src/command.rs | 18 +- controller/src/controller.rs | 63 ++- impls/Cargo.toml | 20 + impls/src/adapters/http.rs | 87 +++- impls/src/adapters/mod.rs | 39 +- impls/src/client_utils/client.rs | 396 ++++++++++++++++++ impls/src/client_utils/mod.rs | 19 + impls/src/client_utils/socksv5.rs | 254 ++++++++++++ impls/src/error.rs | 21 + impls/src/lib.rs | 2 + impls/src/lifecycle/default.rs | 20 +- impls/src/node_clients/http.rs | 67 ++-- impls/src/tor/config.rs | 463 ++++++++++++++++++++++ impls/src/tor/mod.rs | 16 + impls/src/tor/process.rs | 290 ++++++++++++++ libwallet/src/error.rs | 8 + libwallet/src/types.rs | 3 +- src/bin/grin-wallet.yml | 5 + src/cmd/wallet.rs | 13 +- src/cmd/wallet_args.rs | 27 +- tests/cmd_line_basic.rs | 57 ++- tests/common/mod.rs | 59 ++- tests/data/v3_reqs/create_config.req.json | 3 +- tests/owner_v2_sanity.rs | 10 +- tests/owner_v3_lifecycle.rs | 9 +- tests/tor_dev_helper.rs | 101 +++++ util/Cargo.toml | 1 + 34 files changed, 2311 insertions(+), 153 deletions(-) create mode 100644 impls/src/client_utils/client.rs create mode 100644 impls/src/client_utils/mod.rs create mode 100644 impls/src/client_utils/socksv5.rs create mode 100644 impls/src/tor/config.rs create mode 100644 impls/src/tor/mod.rs create mode 100644 impls/src/tor/process.rs create mode 100644 tests/tor_dev_helper.rs diff --git a/Cargo.lock b/Cargo.lock index 89f21b1b6..814d2b3e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -41,16 +41,16 @@ name = "arrayvec" version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", + "nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", "odds 0.2.26 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "arrayvec" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", + "nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -143,7 +143,7 @@ name = "blake2-rfc" version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", + "arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", "constant_time_eq 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -153,7 +153,7 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", + "arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", "constant_time_eq 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -166,6 +166,25 @@ dependencies = [ "byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "block-padding 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "block-padding" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "built" version = "0.3.2" @@ -182,6 +201,11 @@ name = "byte-tools" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "byteorder" version = "1.3.2" @@ -193,7 +217,7 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -264,6 +288,14 @@ dependencies = [ "yaml-rust 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "clear_on_drop" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "cloudabi" version = "0.0.3" @@ -318,7 +350,7 @@ name = "crossbeam-epoch" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", + "arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -379,6 +411,23 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "curve25519-dalek" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "subtle 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "data-encoding" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "difference" version = "2.0.0" @@ -392,6 +441,14 @@ dependencies = [ "generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "dirs" version = "1.0.5" @@ -402,6 +459,11 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "doc-comment" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "dtoa" version = "0.4.4" @@ -409,7 +471,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "easy-jsonrpc" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "easy-jsonrpc-proc-macro 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -453,6 +515,24 @@ dependencies = [ "syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ed25519-dalek" +version = "1.0.0-pre.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "curve25519-dalek 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "either" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "encode_unicode" version = "0.3.6" @@ -511,7 +591,7 @@ dependencies = [ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "miniz_oxide 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "miniz_oxide 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -565,6 +645,14 @@ dependencies = [ "typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "generic-array" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "getrandom" version = "0.1.12" @@ -595,7 +683,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "grin_api" version = "3.0.0-alpha.1" -source = "git+https://github.com/mimblewimble/grin#4e37c1c9e7532150503d496c082cdb5ed8c27315" +source = "git+https://github.com/mimblewimble/grin#da2e75299191acd70d4a48bd63c3dcb2cfcc74d2" dependencies = [ "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -628,7 +716,7 @@ dependencies = [ [[package]] name = "grin_chain" version = "3.0.0-alpha.1" -source = "git+https://github.com/mimblewimble/grin#4e37c1c9e7532150503d496c082cdb5ed8c27315" +source = "git+https://github.com/mimblewimble/grin#da2e75299191acd70d4a48bd63c3dcb2cfcc74d2" dependencies = [ "bitflags 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -651,7 +739,7 @@ dependencies = [ [[package]] name = "grin_core" version = "3.0.0-alpha.1" -source = "git+https://github.com/mimblewimble/grin#4e37c1c9e7532150503d496c082cdb5ed8c27315" +source = "git+https://github.com/mimblewimble/grin#da2e75299191acd70d4a48bd63c3dcb2cfcc74d2" dependencies = [ "blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -678,7 +766,7 @@ dependencies = [ [[package]] name = "grin_keychain" version = "3.0.0-alpha.1" -source = "git+https://github.com/mimblewimble/grin#4e37c1c9e7532150503d496c082cdb5ed8c27315" +source = "git+https://github.com/mimblewimble/grin#da2e75299191acd70d4a48bd63c3dcb2cfcc74d2" dependencies = [ "blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -701,7 +789,7 @@ dependencies = [ [[package]] name = "grin_p2p" version = "3.0.0-alpha.1" -source = "git+https://github.com/mimblewimble/grin#4e37c1c9e7532150503d496c082cdb5ed8c27315" +source = "git+https://github.com/mimblewimble/grin#da2e75299191acd70d4a48bd63c3dcb2cfcc74d2" dependencies = [ "bitflags 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", @@ -712,6 +800,7 @@ dependencies = [ "grin_store 3.0.0-alpha.1 (git+https://github.com/mimblewimble/grin)", "grin_util 3.0.0-alpha.1 (git+https://github.com/mimblewimble/grin)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "lru-cache 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", "num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -723,7 +812,7 @@ dependencies = [ [[package]] name = "grin_pool" version = "3.0.0-alpha.1" -source = "git+https://github.com/mimblewimble/grin#4e37c1c9e7532150503d496c082cdb5ed8c27315" +source = "git+https://github.com/mimblewimble/grin#da2e75299191acd70d4a48bd63c3dcb2cfcc74d2" dependencies = [ "blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", "chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", @@ -757,7 +846,7 @@ dependencies = [ [[package]] name = "grin_store" version = "3.0.0-alpha.1" -source = "git+https://github.com/mimblewimble/grin#4e37c1c9e7532150503d496c082cdb5ed8c27315" +source = "git+https://github.com/mimblewimble/grin#da2e75299191acd70d4a48bd63c3dcb2cfcc74d2" dependencies = [ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "croaring 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", @@ -778,7 +867,7 @@ dependencies = [ [[package]] name = "grin_util" version = "3.0.0-alpha.1" -source = "git+https://github.com/mimblewimble/grin#4e37c1c9e7532150503d496c082cdb5ed8c27315" +source = "git+https://github.com/mimblewimble/grin#da2e75299191acd70d4a48bd63c3dcb2cfcc74d2" dependencies = [ "backtrace 0.3.38 (registry+https://github.com/rust-lang/crates.io-index)", "base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -803,7 +892,7 @@ dependencies = [ "built 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "ctrlc 3.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "easy-jsonrpc 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "easy-jsonrpc 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "grin_wallet_api 3.0.0-alpha.1", @@ -895,23 +984,36 @@ name = "grin_wallet_impls" version = "3.0.0-alpha.1" dependencies = [ "blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", + "data-encoding 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "ed25519-dalek 1.0.0-pre.2 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", "grin_wallet_config 3.0.0-alpha.1", "grin_wallet_libwallet 3.0.0-alpha.1", "grin_wallet_util 3.0.0-alpha.1", + "http 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.12.19 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper-rustls 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper-timeout 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "ring 0.13.5 (registry+https://github.com/rust-lang/crates.io-index)", "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", + "sha3 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", + "sysinfo 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)", + "timer 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "tokio 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-retry 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1022,7 +1124,7 @@ dependencies = [ "h2 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", "http 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", "httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1096,11 +1198,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "iovec" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1130,6 +1231,11 @@ dependencies = [ "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "keccak" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "kernel32-sys" version = "0.2.2" @@ -1264,7 +1370,7 @@ dependencies = [ "serde-value 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_yaml 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_yaml 0.8.11 (registry+https://github.com/rust-lang/crates.io-index)", "thread-id 3.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "typemap 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1315,7 +1421,7 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1328,7 +1434,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1343,7 +1449,7 @@ name = "mio-uds" version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1411,7 +1517,7 @@ dependencies = [ [[package]] name = "nodrop" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -1566,6 +1672,11 @@ name = "odds" version = "0.2.26" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "ordered-float" version = "1.0.2" @@ -1746,7 +1857,7 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1778,7 +1889,7 @@ name = "quote" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1947,6 +2058,28 @@ dependencies = [ "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rayon" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon-core 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rayon-core" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rdrand" version = "0.4.0" @@ -2157,7 +2290,7 @@ name = "serde_derive" version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2174,7 +2307,7 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.8.9" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2194,6 +2327,29 @@ dependencies = [ "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "sha2" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "sha3" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "keccak 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "siphasher" version = "0.2.3" @@ -2251,6 +2407,11 @@ dependencies = [ "syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "subtle" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "supercow" version = "0.1.0" @@ -2271,7 +2432,7 @@ name = "syn" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2287,6 +2448,18 @@ dependencies = [ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "sysinfo" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "doc-comment 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "tempfile" version = "3.1.0" @@ -2365,6 +2538,14 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "timer" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "tokio" version = "0.1.11" @@ -2403,7 +2584,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", "scoped-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2478,7 +2659,7 @@ dependencies = [ "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-sync 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-sync 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2512,7 +2693,7 @@ dependencies = [ [[package]] name = "tokio-sync" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2526,7 +2707,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-reactor 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2580,7 +2761,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2892,7 +3073,7 @@ dependencies = [ "checksum arc-swap 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "bc4662175ead9cd84451d5c35070517777949a2ed84551764129cedb88384841" "checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee" "checksum arrayvec 0.3.25 (registry+https://github.com/rust-lang/crates.io-index)" = "06f59fe10306bb78facd90d28c2038ad23ffaaefa85bac43c8a434cde383334f" -"checksum arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b8d73f9beda665eaa98ab9e4f7442bd4e7de6652587de55b2525e52e29c1b0ba" +"checksum arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" "checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" "checksum autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b671c8fb71b457dd4ae18c4ba1e59aa81793daacc361d82fcd410cef0d491875" "checksum backtrace 0.3.38 (registry+https://github.com/rust-lang/crates.io-index)" = "690a62be8920ccf773ee00ef0968649b0e724cda8bd5b12286302b4ae955fdf5" @@ -2906,8 +3087,11 @@ dependencies = [ "checksum blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" "checksum blake2b_simd 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)" = "5850aeee1552f495dd0250014cf64b82b7c8879a89d83b33bbdace2cc4f63182" "checksum block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a076c298b9ecdb530ed9d967e74a6027d6a7478924520acddcddc24c1c8ab3ab" +"checksum block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +"checksum block-padding 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "6d4dc3af3ee2e12f3e5d224e5e1e3d73668abbeb69e566d361f7d5563a4fdf09" "checksum built 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3d2315cfb416f86e05360edc950b1d7d25ecfb00f7f8eba60dbd7882a0f2e944" "checksum byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40" +"checksum byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" "checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" "checksum bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" "checksum c2-chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7d64d04786e0f528460fc884753cf8dddcc466be308f6026f8e355c41a0e4101" @@ -2917,6 +3101,7 @@ dependencies = [ "checksum chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e8493056968583b0193c1bb04d6f7684586f3726992d6c573261941a895dbd68" "checksum clang-sys 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d7f7c04e52c35222fffcc3a115b5daf5f7e2bfb71c13c4e2321afe1fc71859c2" "checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" +"checksum clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "97276801e127ffb46b66ce23f35cc96bd454fa311294bced4bbace7baa8b1d17" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum constant_time_eq 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "995a44c877f9212528ccc74b21a232f66ad69001e40ede5bcee2ac9ef2657120" "checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" @@ -2930,14 +3115,20 @@ dependencies = [ "checksum csv 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7ef22b37c7a51c564a365892c012dc0271221fdcc64c69b19ba4d6fa8bd96d9c" "checksum ct-logs 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "95a4bf5107667e12bf6ce31a3a5066d67acc88942b6742117a41198734aaccaa" "checksum ctrlc 3.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c7dfd2d8b4c82121dfdff120f818e09fc4380b0b7e17a742081a89b94853e87f" +"checksum curve25519-dalek 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8b7dcd30ba50cdf88b55b033456138b7c0ac4afdc436d82e1b79f370f24cc66d" +"checksum data-encoding 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f4f47ca1860a761136924ddd2422ba77b2ea54fe8cc75b9040804a0d9d32ad97" "checksum difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" "checksum digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "03b072242a8cbaf9c145665af9d250c59af3b958f83ed6824e13533cf76d5b90" +"checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" "checksum dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" +"checksum doc-comment 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "923dea538cea0aa3025e8685b20d6ee21ef99c4f77e954a30febbaac5ec73a97" "checksum dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ea57b42383d091c85abcc2706240b94ab2a8fa1fc81c10ff23c4de06e2a90b5e" -"checksum easy-jsonrpc 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4a851f8e0ed5790b60ded487feb0dc3c7e7da52c4a0adc57c009bfc5af8ca1a" +"checksum easy-jsonrpc 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "07e05c6cb07c5bb6fdedd8de84a96c9e0aafc5a9d4e725b735ca5eddb770ae33" "checksum easy-jsonrpc-mw 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b1a91569d50e3bba3c9febb22ef54d78c6e8a8d8dd91ae859896c8ba05f4e3" "checksum easy-jsonrpc-proc-macro 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9fb33793846951f339a70580375734416898ff8ddbb74401865031e25ba6751" "checksum easy-jsonrpc-proc-macro-mw 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a6368dbd2c6685fb84fc6e6a4749917ddc98905793fd06341c7e11a2504f2724" +"checksum ed25519-dalek 1.0.0-pre.2 (registry+https://github.com/rust-lang/crates.io-index)" = "845aaacc16f01178f33349e7c992ecd0cee095aa5e577f0f4dee35971bd36455" +"checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" "checksum encode_unicode 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" "checksum enum_primitive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "be4551092f4d519593039259a9ed8daedf0da12e5109c5280338073eaeb81180" "checksum env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)" = "15b0a4d2e39f8420210be8b27eeda28029729e2fd4291019455016c348240c38" @@ -2952,6 +3143,7 @@ dependencies = [ "checksum futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)" = "1b980f2816d6ee8673b6517b52cb0e808a180efc92e5c19d02cdda79066703ef" "checksum futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4" "checksum gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)" = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" +"checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" "checksum generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ef25c5683767570c2bbd7deba372926a55eaae9982d7726ee2a1050239d45b9d" "checksum getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "473a1265acc8ff1e808cd0a1af8cee3c2ee5200916058a2ca113c29f2d903571" "checksum git2 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "39f27186fbb5ec67ece9a56990292bc5aed3c3fc51b9b07b0b52446b1dfb4a82" @@ -2977,10 +3169,11 @@ dependencies = [ "checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" "checksum idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" "checksum indexmap 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a61202fbe46c4a951e9404a720a0180bcf3212c750d735cb5c4ba4dc551299f3" -"checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" +"checksum iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" "checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" "checksum jobserver 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "f2b1d42ef453b30b7387e113da1c83ab1605d90c5b4e0eb8e96d016ed3b8c160" "checksum jsonrpc-core 10.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dc15eef5f8b6bef5ac5f7440a957ff95d036e2f98706947741bfc93d1976db4c" +"checksum keccak 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" "checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba" @@ -3002,7 +3195,7 @@ dependencies = [ "checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" "checksum memmap 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" "checksum memoffset 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ce6075db033bbbb7ee5a0bbd3a3186bbae616f57fb001c485c7ff77955f8177f" -"checksum miniz_oxide 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7108aff85b876d06f22503dcce091e29f76733b2bfdd91eebce81f5e68203a10" +"checksum miniz_oxide 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "304f66c19be2afa56530fa7c39796192eef38618da8d19df725ad7c6d6b2aaae" "checksum mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)" = "83f51996a3ed004ef184e16818edc51fadffe8e7ca68be67f9dee67d84d0ff23" "checksum mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125" "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" @@ -3010,7 +3203,7 @@ dependencies = [ "checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" "checksum nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce" "checksum nix 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a0d95c5fa8b641c10ad0b8887454ebaafa3c92b5cd5350f8fc693adafd178e7b" -"checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" +"checksum nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" "checksum nom 3.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05aec50c70fd288702bcd93284a8444607f3292dbdf2a30de5ea5dcdbe72287b" "checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" "checksum num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "4703ad64153382334aa8db57c637364c322d3372e097840c72000dabdcf6156e" @@ -3027,6 +3220,7 @@ dependencies = [ "checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" "checksum num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273" "checksum odds 0.2.26 (registry+https://github.com/rust-lang/crates.io-index)" = "4eae0151b9dacf24fcc170d9995e511669a082856a91f958a2fe380bfab3fb22" +"checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" "checksum ordered-float 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "18869315e81473c951eb56ad5558bbc56978562d3ecfb87abb7a1e944cea4518" "checksum owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49a4b8ea2179e6a2e27411d3bca09ca6dd630821cf6894c6c7c8467a8ee7ef13" "checksum parking_lot 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f0802bff09003b291ba756dc7e79313e51cc31667e94afbe847def490424cde5" @@ -3048,7 +3242,7 @@ dependencies = [ "checksum prettytable-rs 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5511ca4c805aa35f0abff6be7923231d664408b60c09f44ef715f2bce106cd9e" "checksum proc-macro2 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "77997c53ae6edd6d187fec07ec41b207063b5ee6f33680e9fa86d405cdd313d4" "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" -"checksum proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afdc77cc74ec70ed262262942ebb7dac3d479e9e5cfa2da1841c0806f6cdabcc" +"checksum proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "90cf5f418035b98e655e9cdb225047638296b862b42411c4e45bb88d700f7fc0" "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" "checksum quote 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9949cfe66888ffe1d53e6ec9d9f3b70714083854be20fd5e271b232a017401e8" "checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" @@ -3070,6 +3264,8 @@ dependencies = [ "checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" "checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" "checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +"checksum rayon 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "83a27732a533a1be0a0035a111fe76db89ad312f6f0347004c220c57f209a123" +"checksum rayon-core 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "98dcf634205083b17d0861252431eb2acbfb698ab7478a2d20de07954f47ec7b" "checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" "checksum redox_users 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ecedbca3bf205f8d8f5c2b44d83cd0690e39ee84b951ed649e9f1841132b66d" @@ -3099,8 +3295,10 @@ dependencies = [ "checksum serde-value 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7a663f873dedc4eac1a559d4c6bc0d0b2c34dc5ac4702e105014b8281489e44f" "checksum serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)" = "4b133a43a1ecd55d4086bd5b4dc6c1751c68b1bfbeba7a5040442022c7e7c02e" "checksum serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)" = "2f72eb2a68a7dc3f9a691bfda9305a1c017a6215e5a4545c258500d2099a37c2" -"checksum serde_yaml 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)" = "38b08a9a90e5260fe01c6480ec7c811606df6d3a660415808c3c3fa8ed95b582" +"checksum serde_yaml 0.8.11 (registry+https://github.com/rust-lang/crates.io-index)" = "691b17f19fc1ec9d94ec0b5864859290dff279dbd7b03f017afda54eb36c3c35" "checksum sha2 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9eb6be24e4c23a84d7184280d2722f7f2731fcdd4a9d886efbfe4413e4847ea0" +"checksum sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4d8bfd0e469f417657573d8451fb33d16cfe0989359b93baf3a1ffc639543d" +"checksum sha3 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd26bc0e7a2e3a7c959bc494caf58b72ee0c71d67704e9520f736ca7e4853ecf" "checksum siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" "checksum smallstr 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6aa65bb4d5b2bbc90d36af64e29802f788aa614783fa1d0df011800ddcec6e8e" @@ -3110,10 +3308,12 @@ dependencies = [ "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" "checksum strum 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e5d1c33039533f051704951680f1adfd468fd37ac46816ded0d9ee068e60f05f" "checksum strum_macros 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "47cd23f5c7dee395a00fa20135e2ec0fffcdfa151c56182966d7a3261343432e" +"checksum subtle 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab3af2eb31c42e8f0ccf43548232556c42737e01a96db6e1777b0be108e79799" "checksum supercow 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "171758edb47aa306a78dfa4ab9aeb5167405bd4e3dc2b64e88f6a84bbe98bd63" "checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" "checksum syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf" "checksum synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f" +"checksum sysinfo 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d5bd3b813d94552a8033c650691645f8dd5a63d614dddd62428a95d3931ef7b6" "checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" "checksum term 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "edd106a334b7657c10b7c540a0106114feadeb4dc314513e97df481d5d966f42" "checksum termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "96d6098003bde162e4277c70665bd87c326f5a0c3f3fbfb285787fa482d54e6e" @@ -3122,6 +3322,7 @@ dependencies = [ "checksum thread-id 3.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c7fbf4c9d56b320106cd64fd024dadfa0be7cb4706725fc44a7d7ce952d820c1" "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" +"checksum timer 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "31d42176308937165701f50638db1c31586f183f1aab416268216577aec7306b" "checksum tokio 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "6e93c78d23cc61aa245a8acd2c4a79c4d7fa7fb5c3ca90d5737029f043a84895" "checksum tokio-codec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5c501eceaf96f0e1793cf26beb63da3d11c738c4a943fdf3746d81d64684c39f" "checksum tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "aeeffbbb94209023feaef3c196a41cbcdafa06b4a6f893f68779bb5e53796f71" @@ -3134,7 +3335,7 @@ dependencies = [ "checksum tokio-retry 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f05746ae87dca83a2016b4f5dba5b237b897dd12fd324f60afe282112f16969a" "checksum tokio-rustls 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "208d62fa3e015426e3c64039d9d20adf054a3c9b4d9445560f1c41c75bef3eab" "checksum tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "24da22d077e0f15f55162bdbdc661228c1581892f52074fb242678d015b45162" -"checksum tokio-sync 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2162248ff317e2bc713b261f242b69dbb838b85248ed20bb21df56d60ea4cae7" +"checksum tokio-sync 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "d06554cce1ae4a50f42fba8023918afa931413aded705b560e29600ccf7c6d76" "checksum tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1d14b10654be682ac43efee27401d792507e30fd8d26389e1da3b185de2e4119" "checksum tokio-threadpool 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "2bd2c6a3885302581f4401c82af70d792bb9df1700e7437b0aeb4ada94d5388c" "checksum tokio-timer 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "f2106812d500ed25a4f38235b9cae8f78a09edf43203e16e59c3b769a342a60e" diff --git a/api/src/owner.rs b/api/src/owner.rs index 50ace7dcf..91d5a4914 100644 --- a/api/src/owner.rs +++ b/api/src/owner.rs @@ -17,7 +17,7 @@ use chrono::prelude::*; use uuid::Uuid; -use crate::config::WalletConfig; +use crate::config::{TorConfig, WalletConfig}; use crate::core::core::Transaction; use crate::core::global; use crate::impls::create_sender; @@ -579,7 +579,8 @@ where .into()); } }; - let comm_adapter = create_sender(&sa.method, &sa.dest) + //TODO: no TOR just now via this method, to keep compatibility for now + let comm_adapter = create_sender(&sa.method, &sa.dest, None) .map_err(|e| ErrorKind::GenericError(format!("{}", e)))?; slate = comm_adapter.send_tx(&slate)?; self.tx_lock_outputs(keychain_mask, &slate, 0)?; @@ -1361,7 +1362,7 @@ where /// let api_owner = Owner::new(wallet.clone()); /// let _ = api_owner.set_top_level_directory(dir); /// - /// let result = api_owner.create_config(&ChainTypes::Mainnet, None, None); + /// let result = api_owner.create_config(&ChainTypes::Mainnet, None, None, None); /// /// if let Ok(_) = result { /// //... @@ -1373,6 +1374,7 @@ where chain_type: &global::ChainTypes, wallet_config: Option, logging_config: Option, + tor_config: Option, ) -> Result<(), Error> { let mut w_lock = self.wallet_inst.lock(); let lc = w_lock.lc_provider()?; @@ -1381,6 +1383,7 @@ where "grin-wallet.toml", wallet_config, logging_config, + tor_config, ) } @@ -1429,7 +1432,7 @@ where /// let _ = api_owner.set_top_level_directory(dir); /// /// // Create configuration - /// let result = api_owner.create_config(&ChainTypes::Mainnet, None, None); + /// let result = api_owner.create_config(&ChainTypes::Mainnet, None, None, None); /// /// // create new wallet wirh random seed /// let pw = ZeroingString::from("my_password"); @@ -1496,7 +1499,7 @@ where /// let _ = api_owner.set_top_level_directory(dir); /// /// // Create configuration - /// let result = api_owner.create_config(&ChainTypes::Mainnet, None, None); + /// let result = api_owner.create_config(&ChainTypes::Mainnet, None, None, None); /// /// // create new wallet wirh random seed /// let pw = ZeroingString::from("my_password"); diff --git a/api/src/owner_rpc_s.rs b/api/src/owner_rpc_s.rs index 784b43a43..29892e823 100644 --- a/api/src/owner_rpc_s.rs +++ b/api/src/owner_rpc_s.rs @@ -15,7 +15,7 @@ //! JSON-RPC Stub generation for the Owner API use uuid::Uuid; -use crate::config::WalletConfig; +use crate::config::{TorConfig, WalletConfig}; use crate::core::core::Transaction; use crate::core::global; use crate::keychain::{Identifier, Keychain}; @@ -1469,6 +1469,11 @@ pub trait OwnerRpcS { "log_max_size": null, "log_max_files": null, "tui_running": null + }, + "tor_config" : { + "use_tor_listener": true, + "socks_proxy_addr": "127.0.0.1:9050", + "send_config_dir": "." } }, "id": 1 @@ -1492,6 +1497,7 @@ pub trait OwnerRpcS { chain_type: global::ChainTypes, wallet_config: Option, logging_config: Option, + tor_config: Option, ) -> Result<(), ErrorKind>; /** @@ -1912,8 +1918,10 @@ where chain_type: global::ChainTypes, wallet_config: Option, logging_config: Option, + tor_config: Option, ) -> Result<(), ErrorKind> { - Owner::create_config(self, &chain_type, wallet_config, logging_config).map_err(|e| e.kind()) + Owner::create_config(self, &chain_type, wallet_config, logging_config, tor_config) + .map_err(|e| e.kind()) } fn create_wallet( diff --git a/config/src/comments.rs b/config/src/comments.rs index 013278d7b..cb7756658 100644 --- a/config/src/comments.rs +++ b/config/src/comments.rs @@ -190,6 +190,48 @@ fn comments() -> HashMap { .to_string(), ); + retval.insert( + "[tor]".to_string(), + " +######################################### +### TOR CONFIGURATION (Experimental) ### +######################################### +" + .to_string(), + ); + + retval.insert( + "use_tor_listener".to_string(), + " +#Whether to start tor listener on listener startup (default true) +" + .to_string(), + ); + + retval.insert( + "socks_proxy_addr".to_string(), + " +#Address of the running TOR (SOCKS) server +" + .to_string(), + ); + + retval.insert( + "socks_proxy_addr".to_string(), + " +# TOR (SOCKS) proxy server address +" + .to_string(), + ); + + retval.insert( + "send_config_dir".to_string(), + " +#Directory to output TOR configuration to when sending +" + .to_string(), + ); + retval } diff --git a/config/src/config.rs b/config/src/config.rs index f2ae62509..3ae571b18 100644 --- a/config/src/config.rs +++ b/config/src/config.rs @@ -27,8 +27,8 @@ use toml; use crate::comments::insert_comments; use crate::core::global; -use crate::types::WalletConfig; use crate::types::{ConfigError, GlobalWalletConfig, GlobalWalletConfigMembers}; +use crate::types::{TorConfig, WalletConfig}; use crate::util::LoggingConfig; /// Wallet configuration file name @@ -153,6 +153,7 @@ impl Default for GlobalWalletConfigMembers { fn default() -> GlobalWalletConfigMembers { GlobalWalletConfigMembers { logging: Some(LoggingConfig::default()), + tor: Some(TorConfig::default()), wallet: WalletConfig::default(), } } @@ -257,6 +258,14 @@ impl GlobalWalletConfig { .as_mut() .unwrap() .log_file_path = log_path.to_str().unwrap().to_owned(); + let tor_path = wallet_home.clone(); + self.members + .as_mut() + .unwrap() + .tor + .as_mut() + .unwrap() + .send_config_dir = tor_path.to_str().unwrap().to_owned(); } /// Serialize config diff --git a/config/src/lib.rs b/config/src/lib.rs index 06b5ac484..313e0b586 100644 --- a/config/src/lib.rs +++ b/config/src/lib.rs @@ -31,4 +31,6 @@ pub mod config; pub mod types; pub use crate::config::{initial_setup_wallet, GRIN_WALLET_DIR, WALLET_CONFIG_FILE_NAME}; -pub use crate::types::{ConfigError, GlobalWalletConfig, GlobalWalletConfigMembers, WalletConfig}; +pub use crate::types::{ + ConfigError, GlobalWalletConfig, GlobalWalletConfigMembers, TorConfig, WalletConfig, +}; diff --git a/config/src/types.rs b/config/src/types.rs index d6599e38c..1069bf57b 100644 --- a/config/src/types.rs +++ b/config/src/types.rs @@ -138,6 +138,26 @@ impl fmt::Display for ConfigError { } } +/// Tor configuration +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct TorConfig { + /// Whether to start tor listener on listener startup (default true) + pub use_tor_listener: bool, + /// Just the address of the socks proxy for now + pub socks_proxy_addr: String, + /// Send configuration directory + pub send_config_dir: String, +} + +impl Default for TorConfig { + fn default() -> TorConfig { + TorConfig { + use_tor_listener: true, + socks_proxy_addr: "127.0.0.1:59050".to_owned(), + send_config_dir: ".".into(), + } + } +} impl From for ConfigError { fn from(error: io::Error) -> ConfigError { ConfigError::FileIOError( @@ -162,6 +182,8 @@ pub struct GlobalWalletConfigMembers { /// Wallet configuration #[serde(default)] pub wallet: WalletConfig, + /// Tor config + pub tor: Option, /// Logging config pub logging: Option, } diff --git a/controller/src/command.rs b/controller/src/command.rs index 4325e2640..d8a87349f 100644 --- a/controller/src/command.rs +++ b/controller/src/command.rs @@ -15,7 +15,7 @@ //! Grin wallet command-line function implementations use crate::api::TLSConfig; -use crate::config::{WalletConfig, WALLET_CONFIG_FILE_NAME}; +use crate::config::{TorConfig, WalletConfig, WALLET_CONFIG_FILE_NAME}; use crate::core::{core, global}; use crate::error::{Error, ErrorKind}; use crate::impls::{create_sender, KeybaseAllChannels, SlateGetter as _, SlateReceiver as _}; @@ -75,7 +75,13 @@ where { let mut w_lock = wallet.lock(); let p = w_lock.lc_provider()?; - p.create_config(&g_args.chain_type, WALLET_CONFIG_FILE_NAME, None, None)?; + p.create_config( + &g_args.chain_type, + WALLET_CONFIG_FILE_NAME, + None, + None, + None, + )?; p.create_wallet( None, args.recovery_phrase, @@ -125,6 +131,7 @@ pub fn listen<'a, L, C, K>( wallet: Arc>>>, keychain_mask: Arc>>, config: &WalletConfig, + tor_config: &TorConfig, args: &ListenArgs, g_args: &GlobalArgs, ) -> Result<(), Error> @@ -139,6 +146,7 @@ where keychain_mask, &config.api_listen_addr(), g_args.tls_conf.clone(), + tor_config.use_tor_listener, ), "keybase" => KeybaseAllChannels::new()?.listen( config.clone(), @@ -251,6 +259,7 @@ pub struct SendArgs { pub fn send<'a, L, C, K>( wallet: Arc>>>, keychain_mask: Option<&SecretKey>, + tor_config: Option, args: SendArgs, dark_scheme: bool, ) -> Result<(), Error> @@ -327,7 +336,7 @@ where })?; } method => { - let sender = create_sender(method, &args.dest)?; + let sender = create_sender(method, &args.dest, tor_config)?; slate = sender.send_tx(&slate)?; api.tx_lock_outputs(m, &slate, 0)?; } @@ -514,6 +523,7 @@ pub struct ProcessInvoiceArgs { pub fn process_invoice<'a, L, C, K>( wallet: Arc>>>, keychain_mask: Option<&SecretKey>, + tor_config: Option, args: ProcessInvoiceArgs, dark_scheme: bool, ) -> Result<(), Error> @@ -594,7 +604,7 @@ where })?; } method => { - let sender = create_sender(method, &args.dest)?; + let sender = create_sender(method, &args.dest, tor_config)?; slate = sender.send_tx(&slate)?; api.tx_lock_outputs(m, &slate, 0)?; } diff --git a/controller/src/controller.rs b/controller/src/controller.rs index 96a6b29f4..6619a115b 100644 --- a/controller/src/controller.rs +++ b/controller/src/controller.rs @@ -33,6 +33,9 @@ use std::collections::HashMap; use std::net::SocketAddr; use std::sync::Arc; +use crate::impls::tor::config as tor_config; +use crate::impls::tor::process as tor_process; + use crate::apiwallet::{ EncryptedRequest, EncryptedResponse, EncryptionErrorResponse, Foreign, ForeignCheckMiddlewareFn, ForeignRpc, Owner, OwnerRpc, OwnerRpcS, @@ -75,6 +78,47 @@ fn check_middleware( } } +/// initiate the tor listener +fn init_tor_listener( + wallet: Arc + 'static>>>, + keychain_mask: Arc>>, + addr: &str, +) -> Result +where + L: WalletLCProvider<'static, C, K> + 'static, + C: NodeClient + 'static, + K: Keychain + 'static, +{ + let mut process = tor_process::TorProcess::new(); + let mask = keychain_mask.lock(); + // eventually want to read a list of service config keys + let mut w_lock = wallet.lock(); + let lc = w_lock.lc_provider()?; + let w_inst = lc.wallet_inst()?; + let k = w_inst.keychain((&mask).as_ref())?; + let parent_key_id = w_inst.parent_key_id(); + let tor_dir = format!("{}/tor/listener", lc.get_top_level_directory()?); + let sec_key = tor_config::address_derivation_path(&k, &parent_key_id, 0) + .map_err(|e| ErrorKind::TorConfig(format!("{:?}", e).into()))?; + let onion_address = tor_config::onion_address_from_seckey(&sec_key) + .map_err(|e| ErrorKind::TorConfig(format!("{:?}", e).into()))?; + warn!( + "Starting TOR Hidden Service for API listener at address {}, binding to {}", + onion_address, addr + ); + tor_config::output_tor_listener_config(&tor_dir, addr, &vec![sec_key]) + .map_err(|e| ErrorKind::TorConfig(format!("{:?}", e).into()))?; + // Start TOR process + process + .torrc_path(&format!("{}/torrc", tor_dir)) + .working_dir(&tor_dir) + .timeout(20) + .completion_percent(100) + .launch() + .map_err(|e| ErrorKind::TorProcess(format!("{:?}", e).into()))?; + Ok(process) +} + /// Instantiate wallet Owner API for a single-use (command line) call /// Return a function containing a loaded API context to call pub fn owner_single_use<'a, L, F, C, K>( @@ -188,14 +232,28 @@ pub fn foreign_listener( keychain_mask: Arc>>, addr: &str, tls_config: Option, + use_tor: bool, ) -> Result<(), Error> where L: WalletLCProvider<'static, C, K> + 'static, C: NodeClient + 'static, K: Keychain + 'static, { - let api_handler_v2 = ForeignAPIHandlerV2::new(wallet, keychain_mask); + // need to keep in scope while the main listener is running + let _tor_process = match use_tor { + true => match init_tor_listener(wallet.clone(), keychain_mask.clone(), addr) { + Ok(tp) => Some(tp), + Err(e) => { + warn!("Unable to start TOR listener; Check that TOR executable is installed and on your path"); + warn!("Tor Error: {}", e); + warn!("Listener will be available via HTTP only"); + None + } + }, + false => None, + }; + let api_handler_v2 = ForeignAPIHandlerV2::new(wallet, keychain_mask); let mut router = Router::new(); router @@ -210,6 +268,7 @@ where .context(ErrorKind::GenericError( "API thread failed to start".to_string(), ))?; + warn!("HTTP Foreign listener started."); api_thread @@ -679,7 +738,7 @@ where Box::new(parse_body(req).and_then(move |val: serde_json::Value| { let foreign_api = &api as &dyn ForeignRpc; match foreign_api.handle_request(val) { - MaybeReply::Reply(r) => ok(r), + MaybeReply::Reply(r) => ok({ r }), MaybeReply::DontReply => { // Since it's http, we need to return something. We return [] because jsonrpc // clients will parse it as an empty batch response. diff --git a/impls/Cargo.toml b/impls/Cargo.toml index 9843a983b..54295dffc 100644 --- a/impls/Cargo.toml +++ b/impls/Cargo.toml @@ -27,6 +27,26 @@ tokio-retry = "0.1" uuid = { version = "0.7", features = ["serde", "v4"] } chrono = { version = "0.4.4", features = ["serde"] } +#http client (copied from grin) +http = "0.1.5" +hyper-rustls = "0.14" +hyper-timeout = "0.2" + +#Socks/Tor +byteorder = "1" +hyper = "0.12" +#hyper-tls = "0.1" +tokio-tcp = "0.1" +tokio-io = "0.1" +#native-tls = "0.1" +#tokio-tls = "0.1" +ed25519-dalek = "1.0.0-pre.1" +data-encoding = "2" +sha3 = "0.8" +regex = "1.3" +timer = "0.2" +sysinfo = "0.9" + grin_wallet_util = { path = "../util", version = "3.0.0-alpha.1" } grin_wallet_config = { path = "../config", version = "3.0.0-alpha.1" } grin_wallet_libwallet = { path = "../libwallet", version = "3.0.0-alpha.1" } diff --git a/impls/src/adapters/http.rs b/impls/src/adapters/http.rs index 5a1128c30..4f64fd875 100644 --- a/impls/src/adapters/http.rs +++ b/impls/src/adapters/http.rs @@ -13,15 +13,25 @@ // limitations under the License. /// HTTP Wallet 'plugin' implementation -use crate::api; +use crate::client_utils::{Client, ClientError}; use crate::libwallet::{Error, ErrorKind, Slate}; use crate::SlateSender; use serde::Serialize; use serde_json::{json, Value}; +use std::net::SocketAddr; +use std::path::MAIN_SEPARATOR; + +use crate::tor::config as tor_config; +use crate::tor::process as tor_process; + +const TOR_CONFIG_PATH: &'static str = "tor/sender"; #[derive(Clone)] pub struct HttpSlateSender { base_url: String, + use_socks: bool, + socks_proxy_addr: Option, + tor_config_dir: String, } impl HttpSlateSender { @@ -32,10 +42,27 @@ impl HttpSlateSender { } else { Ok(HttpSlateSender { base_url: base_url.to_owned(), + use_socks: false, + socks_proxy_addr: None, + tor_config_dir: String::from(""), }) } } + /// Switch to using socks proxy + pub fn with_socks_proxy( + base_url: &str, + proxy_addr: &str, + tor_config_dir: &str, + ) -> Result { + let mut ret = Self::new(base_url)?; + ret.use_socks = true; + //TODO: Unwrap + ret.socks_proxy_addr = Some(SocketAddr::V4(proxy_addr.parse().unwrap())); + ret.tor_config_dir = tor_config_dir.into(); + Ok(ret) + } + /// Check version of the listening wallet fn check_other_version(&self, url: &str) -> Result<(), Error> { let req = json!({ @@ -45,7 +72,7 @@ impl HttpSlateSender { "params": [] }); - let res: String = post(url, None, &req).map_err(|e| { + let res: String = self.post(url, None, req).map_err(|e| { let mut report = format!("Performing version check (is recipient listening?): {}", e); let err_string = format!("{}", e); if err_string.contains("404") { @@ -92,6 +119,25 @@ impl HttpSlateSender { Ok(()) } + + fn post( + &self, + url: &str, + api_secret: Option, + input: IN, + ) -> Result + where + IN: Serialize, + { + let mut client = Client::new(); + if self.use_socks { + client.use_socks = true; + client.socks_proxy_addr = self.socks_proxy_addr.clone(); + } + let req = client.create_post_request(url, api_secret, &input)?; + let res = client.send_request(req)?; + Ok(res) + } } impl SlateSender for HttpSlateSender { @@ -102,7 +148,30 @@ impl SlateSender for HttpSlateSender { }; let url_str = format!("{}{}v2/foreign", self.base_url, trailing); - debug!("Posting transaction slate to {}", url_str); + // set up tor send process if needed + let mut tor = tor_process::TorProcess::new(); + if self.use_socks { + let tor_dir = format!( + "{}{}{}", + &self.tor_config_dir, MAIN_SEPARATOR, TOR_CONFIG_PATH + ); + warn!( + "Starting TOR Process for send at {:?}", + self.socks_proxy_addr + ); + tor_config::output_tor_sender_config( + &tor_dir, + &self.socks_proxy_addr.unwrap().to_string(), + ) + .map_err(|e| ErrorKind::TorConfig(format!("{:?}", e).into()))?; + // Start TOR process + tor.torrc_path(&format!("{}/torrc", &tor_dir)) + .working_dir(&tor_dir) + .timeout(20) + .completion_percent(100) + .launch() + .map_err(|e| ErrorKind::TorProcess(format!("{:?}", e).into()))?; + } self.check_other_version(&url_str)?; @@ -119,7 +188,7 @@ impl SlateSender for HttpSlateSender { }); trace!("Sending receive_tx request: {}", req); - let res: String = post(&url_str, None, &req).map_err(|e| { + let res: String = self.post(&url_str, None, req).map_err(|e| { let report = format!("Posting transaction slate (is recipient listening?): {}", e); error!("{}", report); ErrorKind::ClientCallback(report) @@ -154,13 +223,3 @@ impl Into for SchemeNotHttp { ErrorKind::GenericError(err_str).into() } } - -pub fn post(url: &str, api_secret: Option, input: &IN) -> Result -where - IN: Serialize, -{ - // TODO: change create_post_request to accept a url instead of a &str - let req = api::client::create_post_request(url, api_secret, input)?; - let res = api::client::send_request(req)?; - Ok(res) -} diff --git a/impls/src/adapters/mod.rs b/impls/src/adapters/mod.rs index e89e69595..717bc2b10 100644 --- a/impls/src/adapters/mod.rs +++ b/impls/src/adapters/mod.rs @@ -13,15 +13,16 @@ // limitations under the License. mod file; -mod http; +pub mod http; mod keybase; pub use self::file::PathToSlate; -pub use self::http::HttpSlateSender; +pub use self::http::{HttpSlateSender, SchemeNotHttp}; pub use self::keybase::{KeybaseAllChannels, KeybaseChannel}; -use crate::config::WalletConfig; +use crate::config::{TorConfig, WalletConfig}; use crate::libwallet::{Error, ErrorKind, Slate}; +use crate::tor::config::complete_tor_address; use crate::util::ZeroingString; /// Sends transactions to a corresponding SlateReceiver @@ -57,15 +58,43 @@ pub trait SlateGetter { } /// select a SlateSender based on method and dest fields from, e.g., SendArgs -pub fn create_sender(method: &str, dest: &str) -> Result, Error> { +pub fn create_sender( + method: &str, + dest: &str, + tor_config: Option, +) -> Result, Error> { let invalid = || { ErrorKind::WalletComms(format!( "Invalid wallet comm type and destination. method: {}, dest: {}", method, dest )) }; + + let mut method = method.into(); + + // will test if this is a tor address and fill out + // the http://[].onion if missing + let dest = match complete_tor_address(dest) { + Ok(d) => { + method = "tor"; + d + } + Err(_) => dest.into(), + }; + Ok(match method { - "http" => Box::new(HttpSlateSender::new(dest).map_err(|_| invalid())?), + "http" => Box::new(HttpSlateSender::new(&dest).map_err(|_| invalid())?), + "tor" => match tor_config { + None => { + return Err( + ErrorKind::WalletComms("Tor Configuration required".to_string()).into(), + ); + } + Some(tc) => Box::new( + HttpSlateSender::with_socks_proxy(&dest, &tc.socks_proxy_addr, &tc.send_config_dir) + .map_err(|_| invalid())?, + ), + }, "keybase" => Box::new(KeybaseChannel::new(dest.to_owned())?), "self" => { return Err(ErrorKind::WalletComms( diff --git a/impls/src/client_utils/client.rs b/impls/src/client_utils/client.rs new file mode 100644 index 000000000..f329fb7a2 --- /dev/null +++ b/impls/src/client_utils/client.rs @@ -0,0 +1,396 @@ +// Copyright 2018 The Grin Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! High level JSON/HTTP client API + +use crate::client_utils::Socksv5Connector; +use crate::util::to_base64; +use failure::{Backtrace, Context, Fail, ResultExt}; +use futures::future::result; +use futures::future::{err, ok, Either}; +use futures::stream::Stream; +use http::uri::{InvalidUri, Uri}; +use hyper::header::{ACCEPT, AUTHORIZATION, CONTENT_TYPE, USER_AGENT}; +use hyper::rt::Future; +use hyper::{self, Body, Request}; +use hyper_rustls; +use hyper_timeout::TimeoutConnector; +use serde::{Deserialize, Serialize}; +use serde_json; +use std::fmt::{self, Display}; +use std::net::SocketAddr; +use std::time::Duration; +use tokio::runtime::Runtime; + +/// Errors that can be returned by an ApiEndpoint implementation. +#[derive(Debug)] +pub struct Error { + inner: Context, +} + +#[derive(Clone, Eq, PartialEq, Debug, Fail)] +pub enum ErrorKind { + #[fail(display = "Internal error: {}", _0)] + Internal(String), + #[fail(display = "Bad arguments: {}", _0)] + Argument(String), + #[fail(display = "Not found.")] + _NotFound, + #[fail(display = "Request error: {}", _0)] + RequestError(String), + #[fail(display = "ResponseError error: {}", _0)] + ResponseError(String), +} + +impl Fail for Error { + fn cause(&self) -> Option<&dyn Fail> { + self.inner.cause() + } + + fn backtrace(&self) -> Option<&Backtrace> { + self.inner.backtrace() + } +} + +impl Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Display::fmt(&self.inner, f) + } +} + +impl Error { + pub fn _kind(&self) -> &ErrorKind { + self.inner.get_context() + } +} + +impl From for Error { + fn from(kind: ErrorKind) -> Error { + Error { + inner: Context::new(kind), + } + } +} + +impl From> for Error { + fn from(inner: Context) -> Error { + Error { inner: inner } + } +} + +pub type ClientResponseFuture = Box + Send>; + +pub struct Client { + /// Whether to use socks proxy + pub use_socks: bool, + /// Proxy url/port + pub socks_proxy_addr: Option, +} + +impl Client { + /// New client + pub fn new() -> Self { + Client { + use_socks: false, + socks_proxy_addr: None, + } + } + + /// Helper function to easily issue a HTTP GET request against a given URL that + /// returns a JSON object. Handles request building, JSON deserialization and + /// response code checking. + pub fn get<'a, T>(&self, url: &'a str, api_secret: Option) -> Result + where + for<'de> T: Deserialize<'de>, + { + self.handle_request(self.build_request(url, "GET", api_secret, None)?) + } + + /// Helper function to easily issue an async HTTP GET request against a given + /// URL that returns a future. Handles request building, JSON deserialization + /// and response code checking. + pub fn get_async<'a, T>( + &self, + url: &'a str, + api_secret: Option, + ) -> ClientResponseFuture + where + for<'de> T: Deserialize<'de> + Send + 'static, + { + match self.build_request(url, "GET", api_secret, None) { + Ok(req) => Box::new(self.handle_request_async(req)), + Err(e) => Box::new(err(e)), + } + } + + /// Helper function to easily issue a HTTP GET request + /// on a given URL that returns nothing. Handles request + /// building and response code checking. + pub fn _get_no_ret(&self, url: &str, api_secret: Option) -> Result<(), Error> { + let req = self.build_request(url, "GET", api_secret, None)?; + self.send_request(req)?; + Ok(()) + } + + /// Helper function to easily issue a HTTP POST request with the provided JSON + /// object as body on a given URL that returns a JSON object. Handles request + /// building, JSON serialization and deserialization, and response code + /// checking. + pub fn _post( + &self, + url: &str, + api_secret: Option, + input: &IN, + ) -> Result + where + IN: Serialize, + for<'de> OUT: Deserialize<'de>, + { + let req = self.create_post_request(url, api_secret, input)?; + self.handle_request(req) + } + + /// Helper function to easily issue an async HTTP POST request with the + /// provided JSON object as body on a given URL that returns a future. Handles + /// request building, JSON serialization and deserialization, and response code + /// checking. + pub fn _post_async( + &self, + url: &str, + input: &IN, + api_secret: Option, + ) -> ClientResponseFuture + where + IN: Serialize, + OUT: Send + 'static, + for<'de> OUT: Deserialize<'de>, + { + match self.create_post_request(url, api_secret, input) { + Ok(req) => Box::new(self.handle_request_async(req)), + Err(e) => Box::new(err(e)), + } + } + + /// Helper function to easily issue a HTTP POST request with the provided JSON + /// object as body on a given URL that returns nothing. Handles request + /// building, JSON serialization, and response code + /// checking. + pub fn post_no_ret( + &self, + url: &str, + api_secret: Option, + input: &IN, + ) -> Result<(), Error> + where + IN: Serialize, + { + let req = self.create_post_request(url, api_secret, input)?; + self.send_request(req)?; + Ok(()) + } + + /// Helper function to easily issue an async HTTP POST request with the + /// provided JSON object as body on a given URL that returns a future. Handles + /// request building, JSON serialization and deserialization, and response code + /// checking. + pub fn _post_no_ret_async( + &self, + url: &str, + api_secret: Option, + input: &IN, + ) -> ClientResponseFuture<()> + where + IN: Serialize, + { + match self.create_post_request(url, api_secret, input) { + Ok(req) => Box::new(self.send_request_async(req).and_then(|_| ok(()))), + Err(e) => Box::new(err(e)), + } + } + + fn build_request( + &self, + url: &str, + method: &str, + api_secret: Option, + body: Option, + ) -> Result, Error> { + let uri = url.parse::().map_err::(|e: InvalidUri| { + e.context(ErrorKind::Argument(format!("Invalid url {}", url))) + .into() + })?; + let mut builder = Request::builder(); + if let Some(api_secret) = api_secret { + let basic_auth = format!("Basic {}", to_base64(&format!("grin:{}", api_secret))); + builder.header(AUTHORIZATION, basic_auth); + } + + builder + .method(method) + .uri(uri) + .header(USER_AGENT, "grin-client") + .header(ACCEPT, "application/json") + .header(CONTENT_TYPE, "application/json") + .body(match body { + None => Body::empty(), + Some(json) => json.into(), + }) + .map_err(|e| { + ErrorKind::RequestError(format!("Bad request {} {}: {}", method, url, e)).into() + }) + } + + pub fn create_post_request( + &self, + url: &str, + api_secret: Option, + input: &IN, + ) -> Result, Error> + where + IN: Serialize, + { + let json = serde_json::to_string(input).context(ErrorKind::Internal( + "Could not serialize data to JSON".to_owned(), + ))?; + self.build_request(url, "POST", api_secret, Some(json)) + } + + fn handle_request(&self, req: Request) -> Result + where + for<'de> T: Deserialize<'de>, + { + let data = self.send_request(req)?; + serde_json::from_str(&data).map_err(|e| { + e.context(ErrorKind::ResponseError("Cannot parse response".to_owned())) + .into() + }) + } + + fn handle_request_async(&self, req: Request) -> ClientResponseFuture + where + for<'de> T: Deserialize<'de> + Send + 'static, + { + Box::new(self.send_request_async(req).and_then(|data| { + serde_json::from_str(&data).map_err(|e| { + e.context(ErrorKind::ResponseError("Cannot parse response".to_owned())) + .into() + }) + })) + } + + fn send_request_async( + &self, + req: Request, + ) -> Box + Send> { + //TODO: redundant code, enjoy figuring out type params for dynamic dispatch of client + match self.use_socks { + false => { + let https = hyper_rustls::HttpsConnector::new(1); + let mut connector = TimeoutConnector::new(https); + connector.set_connect_timeout(Some(Duration::from_secs(20))); + connector.set_read_timeout(Some(Duration::from_secs(20))); + connector.set_write_timeout(Some(Duration::from_secs(20))); + let client = hyper::Client::builder().build::<_, hyper::Body>(connector); + Box::new( + client + .request(req) + .map_err(|e| { + ErrorKind::RequestError(format!("Cannot make request: {}", e)).into() + }) + .and_then(|resp| { + if !resp.status().is_success() { + Either::A(err(ErrorKind::RequestError(format!( + "Wrong response code: {} with data {:?}", + resp.status(), + resp.body() + )) + .into())) + } else { + Either::B( + resp.into_body() + .map_err(|e| { + ErrorKind::RequestError(format!( + "Cannot read response body: {}", + e + )) + .into() + }) + .concat2() + .and_then(|ch| { + ok(String::from_utf8_lossy(&ch.to_vec()).to_string()) + }), + ) + } + }), + ) + } + true => { + let addr = match self.socks_proxy_addr { + Some(a) => a, + None => { + return Box::new(result(Err(ErrorKind::RequestError(format!( + "Can't parse Socks proxy address" + )) + .into()))) + } + }; + let socks_connector = Socksv5Connector::new(addr); + let mut connector = TimeoutConnector::new(socks_connector); + connector.set_connect_timeout(Some(Duration::from_secs(20))); + connector.set_read_timeout(Some(Duration::from_secs(20))); + connector.set_write_timeout(Some(Duration::from_secs(20))); + let client = hyper::Client::builder().build::<_, hyper::Body>(connector); + Box::new( + client + .request(req) + .map_err(|e| { + ErrorKind::RequestError(format!("Cannot make request: {}", e)).into() + }) + .and_then(|resp| { + if !resp.status().is_success() { + Either::A(err(ErrorKind::RequestError(format!( + "Wrong response code: {} with data {:?}", + resp.status(), + resp.body() + )) + .into())) + } else { + Either::B( + resp.into_body() + .map_err(|e| { + ErrorKind::RequestError(format!( + "Cannot read response body: {}", + e + )) + .into() + }) + .concat2() + .and_then(|ch| { + ok(String::from_utf8_lossy(&ch.to_vec()).to_string()) + }), + ) + } + }), + ) + } + } + } + + pub fn send_request(&self, req: Request) -> Result { + let task = self.send_request_async(req); + let mut rt = + Runtime::new().context(ErrorKind::Internal("can't create Tokio runtime".to_owned()))?; + Ok(rt.block_on(task)?) + } +} diff --git a/impls/src/client_utils/mod.rs b/impls/src/client_utils/mod.rs new file mode 100644 index 000000000..9966e837c --- /dev/null +++ b/impls/src/client_utils/mod.rs @@ -0,0 +1,19 @@ +// Copyright 2019 The Grin Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +mod client; +mod socksv5; + +pub use self::socksv5::Socksv5Connector; +pub use client::{Client, Error as ClientError}; diff --git a/impls/src/client_utils/socksv5.rs b/impls/src/client_utils/socksv5.rs new file mode 100644 index 000000000..9d93b02f8 --- /dev/null +++ b/impls/src/client_utils/socksv5.rs @@ -0,0 +1,254 @@ +// MIT License +// +// Copyright (c) 2017 Vesa Vilhonen +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// Copyright 2019 The Grin Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use byteorder::{BigEndian, WriteBytesExt}; +use futures::future::ok; +use futures::{Future, IntoFuture}; +//use hyper_tls::MaybeHttpsStream; +//use native_tls::TlsConnector; +use std::io::{self, Error, ErrorKind, Write}; +use std::net::SocketAddr; +use tokio_io::io::{read_exact, write_all}; +use tokio_tcp::TcpStream; +//use tokio_tls::TlsConnectorExt; +use hyper::client::connect::{Connect, Connected, Destination}; + +pub struct Socksv5Connector { + proxy_addr: SocketAddr, + creds: Option<(Vec, Vec)>, +} + +impl Socksv5Connector { + pub fn new(proxy_addr: SocketAddr) -> Socksv5Connector { + Socksv5Connector { + proxy_addr, + creds: None, + } + } + + pub fn _new_with_creds>>( + proxy_addr: SocketAddr, + creds: (T, T), + ) -> io::Result { + let username = creds.0.into(); + let password = creds.1.into(); + if username.len() > 255 || password.len() > 255 { + Err(Error::new(ErrorKind::Other, "invalid credentials")) + } else { + Ok(Socksv5Connector { + proxy_addr, + creds: Some((username, password)), + }) + } + } +} + +impl Connect for Socksv5Connector { + type Transport = TcpStream; + type Error = Error; + type Future = Box + Send>; + + fn connect(&self, dst: Destination) -> Self::Future { + let creds = self.creds.clone(); + Box::new( + TcpStream::connect(&self.proxy_addr) + .and_then(move |socket| do_handshake(socket, dst, creds)), + ) + } +} + +type HandshakeFutureConnected = Box + Send>; +type HandshakeFuture = Box + Send>; + +fn auth_negotiation( + socket: TcpStream, + creds: Option<(Vec, Vec)>, +) -> HandshakeFuture { + let (username, password) = creds.unwrap(); + let mut creds_msg: Vec = Vec::with_capacity(username.len() + password.len() + 3); + creds_msg.push(1); + creds_msg.push(username.len() as u8); + creds_msg.extend_from_slice(&username); + creds_msg.push(password.len() as u8); + creds_msg.extend_from_slice(&password); + Box::new( + write_all(socket, creds_msg) + .and_then(|(socket, _)| read_exact(socket, [0; 2])) + .and_then(|(socket, resp)| { + if resp[0] == 1 && resp[1] == 0 { + Ok(socket) + } else { + Err(Error::new(ErrorKind::InvalidData, "unauthorized")) + } + }), + ) +} + +fn answer_hello( + socket: TcpStream, + response: [u8; 2], + creds: Option<(Vec, Vec)>, +) -> HandshakeFuture { + if response[0] == 5 && response[1] == 0 { + Box::new(ok(socket)) + } else if response[0] == 5 && response[1] == 2 && creds.is_some() { + Box::new(auth_negotiation(socket, creds).and_then(|socket| ok(socket))) + } else { + Box::new( + Err(Error::new( + ErrorKind::InvalidData, + "wrong response from socks server", + )) + .into_future(), + ) + } +} + +fn write_addr(socket: TcpStream, req: Destination) -> HandshakeFuture { + let host = req.host(); + if host.len() > u8::max_value() as usize { + return Box::new(Err(Error::new(ErrorKind::InvalidInput, "Host too long")).into_future()); + } + + let port = match req.port() { + Some(port) => port, + _ if req.scheme() == "https" => 443, + _ if req.scheme() == "http" => 80, + _ => { + return Box::new( + Err(Error::new( + ErrorKind::InvalidInput, + "Supports only http/https", + )) + .into_future(), + ) + } + }; + + let mut packet = Vec::new(); + packet.write_all(&vec![5, 1, 0]).unwrap(); + + packet.write_u8(3).unwrap(); + packet.write_u8(host.as_bytes().len() as u8).unwrap(); + packet.write_all(host.as_bytes()).unwrap(); + packet.write_u16::(port).unwrap(); + + Box::new(write_all(socket, packet).map(|(socket, _)| socket)) +} + +fn read_response(socket: TcpStream, response: [u8; 3]) -> HandshakeFuture { + if response[0] != 5 { + return Box::new(Err(Error::new(ErrorKind::Other, "invalid version")).into_future()); + } + match response[1] { + 0 => {} + 1 => { + return Box::new( + Err(Error::new(ErrorKind::Other, "general SOCKS server failure")).into_future(), + ) + } + 2 => { + return Box::new( + Err(Error::new( + ErrorKind::Other, + "connection not allowed by ruleset", + )) + .into_future(), + ) + } + 3 => { + return Box::new(Err(Error::new(ErrorKind::Other, "network unreachable")).into_future()) + } + 4 => return Box::new(Err(Error::new(ErrorKind::Other, "host unreachable")).into_future()), + 5 => { + return Box::new(Err(Error::new(ErrorKind::Other, "connection refused")).into_future()) + } + 6 => return Box::new(Err(Error::new(ErrorKind::Other, "TTL expired")).into_future()), + 7 => { + return Box::new( + Err(Error::new(ErrorKind::Other, "command not supported")).into_future(), + ) + } + 8 => { + return Box::new( + Err(Error::new(ErrorKind::Other, "address kind not supported")).into_future(), + ) + } + _ => return Box::new(Err(Error::new(ErrorKind::Other, "unknown error")).into_future()), + }; + + if response[2] != 0 { + return Box::new( + Err(Error::new(ErrorKind::InvalidData, "invalid reserved byt")).into_future(), + ); + } + + Box::new( + read_exact(socket, [0; 1]) + .and_then(|(socket, response)| match response[0] { + 1 => read_exact(socket, [0; 6]), + _ => unimplemented!(), + }) + .map(|(socket, _)| socket), + ) +} + +fn do_handshake( + socket: TcpStream, + req: Destination, + creds: Option<(Vec, Vec)>, +) -> HandshakeFutureConnected { + let _is_https = req.scheme() == "https"; + let _host = req.host(); + let method: u8 = creds.clone().map(|_| 2).unwrap_or(0); + let established = write_all(socket, [5, 1, method]) + .and_then(|(socket, _)| read_exact(socket, [0; 2])) + .and_then(|(socket, response)| answer_hello(socket, response, creds)) + .and_then(|socket| write_addr(socket, req)) + .and_then(|socket| read_exact(socket, [0; 3])) + .and_then(|(socket, response)| read_response(socket, response)); + /*if is_https { + Box::new(established.and_then(move |socket| { + let tls = TlsConnector::builder().unwrap().build().unwrap(); + tls.connect_async(&host, socket) + .map_err(|err| Error::new(ErrorKind::Other, err)) + .map(|socket| MaybeHttpsStream::Https(socket)) + })) + } else {*/ + //Box::new(established.map(|socket| TcpStream::Http(socket))) + Box::new(established.map(|socket| (socket, Connected::new()))) + /*}*/ +} diff --git a/impls/src/error.rs b/impls/src/error.rs index d3a8c2d9d..00f7e4e9e 100644 --- a/impls/src/error.rs +++ b/impls/src/error.rs @@ -16,6 +16,7 @@ use crate::core::libtx; use crate::keychain; use crate::libwallet; +use crate::util::secp; use failure::{Backtrace, Context, Fail}; use std::env; use std::fmt::{self, Display}; @@ -45,6 +46,10 @@ pub enum ErrorKind { #[fail(display = "IO error")] IO, + /// Secp Error + #[fail(display = "Secp error")] + Secp(secp::Error), + /// Error when formatting json #[fail(display = "Serde JSON error")] Format, @@ -73,6 +78,14 @@ pub enum ErrorKind { #[fail(display = "{}", _0)] ArgumentError(String), + /// Generating ED25519 Public Key + #[fail(display = "Error generating ed25519 secret key: {}", _0)] + ED25519Key(String), + + /// Checking for onion address + #[fail(display = "Address is not an Onion v3 Address")] + NotOnion, + /// Other #[fail(display = "Generic error: {}", _0)] GenericError(String), @@ -151,6 +164,14 @@ impl From for Error { } } +impl From for Error { + fn from(error: secp::Error) -> Error { + Error { + inner: Context::new(ErrorKind::Secp(error)), + } + } +} + impl From for Error { fn from(error: libwallet::Error) -> Error { Error { diff --git a/impls/src/lib.rs b/impls/src/lib.rs index e28bab22f..f576bb3ed 100644 --- a/impls/src/lib.rs +++ b/impls/src/lib.rs @@ -34,10 +34,12 @@ use grin_wallet_config as config; mod adapters; mod backends; +mod client_utils; mod error; mod lifecycle; mod node_clients; pub mod test_framework; +pub mod tor; pub use crate::adapters::{ create_sender, HttpSlateSender, KeybaseAllChannels, KeybaseChannel, PathToSlate, SlateGetter, diff --git a/impls/src/lifecycle/default.rs b/impls/src/lifecycle/default.rs index 8e1072e97..a490bf408 100644 --- a/impls/src/lifecycle/default.rs +++ b/impls/src/lifecycle/default.rs @@ -15,7 +15,7 @@ //! Default wallet lifecycle provider use crate::config::{ - config, GlobalWalletConfig, GlobalWalletConfigMembers, WalletConfig, GRIN_WALLET_DIR, + config, GlobalWalletConfig, GlobalWalletConfigMembers, TorConfig, WalletConfig, GRIN_WALLET_DIR, }; use crate::core::global; use crate::keychain::Keychain; @@ -74,6 +74,7 @@ where file_name: &str, wallet_config: Option, logging_config: Option, + tor_config: Option, ) -> Result<(), Error> { let mut default_config = GlobalWalletConfig::for_chain(chain_type); let logging = match logging_config { @@ -85,13 +86,24 @@ where }; let wallet = match wallet_config { Some(w) => w, - None => match default_config.members { - Some(m) => m.wallet, + None => match default_config.members.as_ref() { + Some(m) => m.clone().wallet.clone(), None => WalletConfig::default(), }, }; + let tor = match tor_config { + Some(t) => Some(t), + None => match default_config.members.as_ref() { + Some(m) => m.clone().tor.clone(), + None => Some(TorConfig::default()), + }, + }; default_config = GlobalWalletConfig { - members: Some(GlobalWalletConfigMembers { wallet, logging }), + members: Some(GlobalWalletConfigMembers { + wallet, + tor, + logging, + }), ..default_config }; let mut config_file_name = PathBuf::from(self.data_dir.clone()); diff --git a/impls/src/node_clients/http.rs b/impls/src/node_clients/http.rs index a3ea8be96..2ddb1ecfa 100644 --- a/impls/src/node_clients/http.rs +++ b/impls/src/node_clients/http.rs @@ -17,14 +17,14 @@ use futures::{stream, Stream}; -use crate::api::LocatedTxKernel; +use crate::api::{self, LocatedTxKernel}; use crate::core::core::TxKernel; use crate::libwallet::{NodeClient, NodeVersionInfo, TxWrapper}; use semver::Version; use std::collections::HashMap; use tokio::runtime::Runtime; -use crate::api; +use crate::client_utils::Client; use crate::libwallet; use crate::util::secp::pedersen; use crate::util::{self, to_hex}; @@ -73,25 +73,25 @@ impl NodeClient for HTTPNodeClient { return Some(v.clone()); } let url = format!("{}/v1/version", self.node_url()); - let mut retval = - match api::client::get::(url.as_str(), self.node_api_secret()) { - Ok(n) => n, - Err(e) => { - // If node isn't available, allow offline functions - // unfortunately have to parse string due to error structure - let err_string = format!("{}", e); - if err_string.contains("404") { - return Some(NodeVersionInfo { - node_version: "1.0.0".into(), - block_header_version: 1, - verified: Some(false), - }); - } else { - error!("Unable to contact Node to get version info: {}", e); - return None; - } + let client = Client::new(); + let mut retval = match client.get::(url.as_str(), self.node_api_secret()) { + Ok(n) => n, + Err(e) => { + // If node isn't available, allow offline functions + // unfortunately have to parse string due to error structure + let err_string = format!("{}", e); + if err_string.contains("404") { + return Some(NodeVersionInfo { + node_version: "1.0.0".into(), + block_header_version: 1, + verified: Some(false), + }); + } else { + error!("Unable to contact Node to get version info: {}", e); + return None; } - }; + } + }; retval.verified = Some(true); self.node_version_info = Some(retval.clone()); Some(retval) @@ -106,7 +106,8 @@ impl NodeClient for HTTPNodeClient { } else { url = format!("{}/v1/pool/push_tx", dest); } - let res = api::client::post_no_ret(url.as_str(), self.node_api_secret(), tx); + let client = Client::new(); + let res = client.post_no_ret(url.as_str(), self.node_api_secret(), tx); if let Err(e) = res { let report = format!("Posting transaction to node: {}", e); error!("Post TX Error: {}", e); @@ -119,7 +120,8 @@ impl NodeClient for HTTPNodeClient { fn get_chain_height(&self) -> Result { let addr = self.node_url(); let url = format!("{}/v1/chain", addr); - let res = api::client::get::(url.as_str(), self.node_api_secret()); + let client = Client::new(); + let res = client.get::(url.as_str(), self.node_api_secret()); match res { Err(e) => { let report = format!("Getting chain height from node: {}", e); @@ -171,10 +173,10 @@ impl NodeClient for HTTPNodeClient { to_hex(excess.0.to_vec()), query ); - let res: Option = api::client::get(url.as_str(), self.node_api_secret()) - .map_err(|e| { - libwallet::ErrorKind::ClientCallback(format!("Kernel lookup: {}", e)) - })?; + let client = Client::new(); + let res: Option = client + .get(url.as_str(), self.node_api_secret()) + .map_err(|e| libwallet::ErrorKind::ClientCallback(format!("Kernel lookup: {}", e)))?; Ok(res.map(|k| (k.tx_kernel, k.height, k.mmr_index))) } @@ -196,12 +198,11 @@ impl NodeClient for HTTPNodeClient { let mut api_outputs: HashMap = HashMap::new(); let mut tasks = Vec::new(); + let client = Client::new(); + for query_chunk in query_params.chunks(200) { let url = format!("{}/v1/chain/outputs/byids?{}", addr, query_chunk.join("&"),); - tasks.push(api::client::get_async::>( - url.as_str(), - self.node_api_secret(), - )); + tasks.push(client.get_async::>(url.as_str(), self.node_api_secret())); } let task = stream::futures_unordered(tasks).collect(); @@ -247,7 +248,9 @@ impl NodeClient for HTTPNodeClient { let mut api_outputs: Vec<(pedersen::Commitment, pedersen::RangeProof, bool, u64, u64)> = Vec::new(); - match api::client::get::(url.as_str(), self.node_api_secret()) { + let client = Client::new(); + + match client.get::(url.as_str(), self.node_api_secret()) { Ok(o) => { for out in o.outputs { let is_coinbase = match out.output_type { @@ -299,7 +302,7 @@ pub fn create_coinbase(dest: &str, block_fees: &BlockFees) -> Result Result { - let res = api::client::post(url, None, block_fees).context(ErrorKind::GenericError( + let res = Client::post(url, None, block_fees).context(ErrorKind::GenericError( "Posting create coinbase".to_string(), ))?; Ok(res) diff --git a/impls/src/tor/config.rs b/impls/src/tor/config.rs new file mode 100644 index 000000000..dd0e74ece --- /dev/null +++ b/impls/src/tor/config.rs @@ -0,0 +1,463 @@ +// Copyright 2019 The Grin Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Tor Configuration + Onion (Hidden) Service operations +use crate::util::secp::key::SecretKey; +use crate::{Error, ErrorKind}; +use grin_wallet_util::grin_keychain::{ChildNumber, Identifier, Keychain, SwitchCommitmentType}; + +use data_encoding::BASE32; +use ed25519_dalek::ExpandedSecretKey; +use ed25519_dalek::PublicKey as DalekPublicKey; +use ed25519_dalek::SecretKey as DalekSecretKey; +use sha3::{Digest, Sha3_256}; + +use crate::blake2::blake2b::blake2b; + +use std::fs::{self, File}; +use std::io::Write; +use std::path::{Path, MAIN_SEPARATOR}; + +use failure::ResultExt; + +const SEC_KEY_FILE: &'static str = "hs_ed25519_secret_key"; +const PUB_KEY_FILE: &'static str = "hs_ed25519_public_key"; +const HOSTNAME_FILE: &'static str = "hostname"; +const TORRC_FILE: &'static str = "torrc"; +const TOR_DATA_DIR: &'static str = "data"; +const AUTH_CLIENTS_DIR: &'static str = "authorized_clients"; +const HIDDEN_SERVICES_DIR: &'static str = "onion_service_addresses"; + +#[cfg(unix)] +fn set_permissions(file_path: &str) -> Result<(), Error> { + use std::os::unix::prelude::*; + fs::set_permissions(file_path, fs::Permissions::from_mode(0o700)).context(ErrorKind::IO)?; + Ok(()) +} + +#[cfg(windows)] +fn set_permissions(_file_path: &str) -> Result<(), Error> { + Ok(()) +} + +struct TorRcConfigItem { + pub name: String, + pub value: String, +} + +impl TorRcConfigItem { + /// Create new + pub fn new(name: &str, value: &str) -> Self { + Self { + name: name.into(), + value: value.into(), + } + } +} + +struct TorRcConfig { + pub items: Vec, +} + +impl TorRcConfig { + /// Create new + pub fn new() -> Self { + Self { items: vec![] } + } + + /// add item + pub fn add_item(&mut self, name: &str, value: &str) { + self.items.push(TorRcConfigItem::new(name, value)); + } + + /// write to file + pub fn write_to_file(&self, file_path: &str) -> Result<(), Error> { + let mut file = File::create(file_path).context(ErrorKind::IO)?; + for item in &self.items { + file.write_all(item.name.as_bytes()) + .context(ErrorKind::IO)?; + file.write_all(b" ").context(ErrorKind::IO)?; + file.write_all(item.value.as_bytes()) + .context(ErrorKind::IO)?; + file.write_all(b"\n").context(ErrorKind::IO)?; + } + Ok(()) + } +} + +/// Output ed25519 keypair given an rust_secp256k1 SecretKey +pub fn ed25519_keypair(sec_key: &SecretKey) -> Result<(DalekSecretKey, DalekPublicKey), Error> { + let d_skey = match DalekSecretKey::from_bytes(&sec_key.0) { + Ok(k) => k, + Err(e) => { + return Err(ErrorKind::ED25519Key(format!("{}", e)).to_owned())?; + } + }; + let d_pub_key: DalekPublicKey = (&d_skey).into(); + Ok((d_skey, d_pub_key)) +} + +/// helper to get address +pub fn onion_address_from_seckey(sec_key: &SecretKey) -> Result { + let (_, d_pub_key) = ed25519_keypair(sec_key)?; + onion_address(&d_pub_key) +} + +/// Generate an onion address from an ed25519_dalek public key +pub fn onion_address(pub_key: &DalekPublicKey) -> Result { + // calculate checksum + let mut hasher = Sha3_256::new(); + hasher.input(b".onion checksum"); + hasher.input(pub_key.as_bytes()); + hasher.input([0x03u8]); + let checksum = hasher.result(); + + let mut address_bytes = pub_key.as_bytes().to_vec(); + address_bytes.push(checksum[0]); + address_bytes.push(checksum[1]); + address_bytes.push(0x03u8); + + let ret = BASE32.encode(&address_bytes); + Ok(ret.to_lowercase()) +} + +pub fn create_onion_service_sec_key_file( + os_directory: &str, + sec_key: &DalekSecretKey, +) -> Result<(), Error> { + let key_file_path = &format!("{}{}{}", os_directory, MAIN_SEPARATOR, SEC_KEY_FILE); + let mut file = File::create(key_file_path).context(ErrorKind::IO)?; + // Tag is always 32 bytes, so pad with null zeroes + file.write("== ed25519v1-secret: type0 ==\0\0\0".as_bytes()) + .context(ErrorKind::IO)?; + let expanded_skey: ExpandedSecretKey = ExpandedSecretKey::from(sec_key); + file.write_all(&expanded_skey.to_bytes()) + .context(ErrorKind::IO)?; + Ok(()) +} + +pub fn create_onion_service_pub_key_file( + os_directory: &str, + pub_key: &DalekPublicKey, +) -> Result<(), Error> { + let key_file_path = &format!("{}{}{}", os_directory, MAIN_SEPARATOR, PUB_KEY_FILE); + let mut file = File::create(key_file_path).context(ErrorKind::IO)?; + // Tag is always 32 bytes, so pad with null zeroes + file.write("== ed25519v1-public: type0 ==\0\0\0".as_bytes()) + .context(ErrorKind::IO)?; + file.write_all(pub_key.as_bytes()).context(ErrorKind::IO)?; + Ok(()) +} + +pub fn create_onion_service_hostname_file(os_directory: &str, hostname: &str) -> Result<(), Error> { + let file_path = &format!("{}{}{}", os_directory, MAIN_SEPARATOR, HOSTNAME_FILE); + let mut file = File::create(file_path).context(ErrorKind::IO)?; + file.write_all(&format!("{}.onion\n", hostname).as_bytes()) + .context(ErrorKind::IO)?; + Ok(()) +} + +pub fn create_onion_auth_clients_dir(os_directory: &str) -> Result<(), Error> { + let auth_dir_path = &format!("{}{}{}", os_directory, MAIN_SEPARATOR, AUTH_CLIENTS_DIR); + fs::create_dir_all(auth_dir_path).context(ErrorKind::IO)?; + Ok(()) +} +/// output an onion service config for the secret key, and return the address +pub fn output_onion_service_config( + tor_config_directory: &str, + sec_key: &SecretKey, +) -> Result { + let (_, d_pub_key) = ed25519_keypair(&sec_key)?; + let address = onion_address(&d_pub_key)?; + let hs_dir_file_path = format!( + "{}{}{}{}{}", + tor_config_directory, MAIN_SEPARATOR, HIDDEN_SERVICES_DIR, MAIN_SEPARATOR, address + ); + + // If file already exists, don't overwrite it, just return address + if Path::new(&hs_dir_file_path).exists() { + return Ok(address); + } + + // create directory if it doesn't exist + fs::create_dir_all(&hs_dir_file_path).context(ErrorKind::IO)?; + + let (d_sec_key, d_pub_key) = ed25519_keypair(&sec_key)?; + create_onion_service_sec_key_file(&hs_dir_file_path, &d_sec_key)?; + create_onion_service_pub_key_file(&hs_dir_file_path, &d_pub_key)?; + create_onion_service_hostname_file(&hs_dir_file_path, &address)?; + create_onion_auth_clients_dir(&hs_dir_file_path)?; + + set_permissions(&hs_dir_file_path)?; + + Ok(address) +} + +/// output torrc file given a list of hidden service directories +pub fn output_torrc( + tor_config_directory: &str, + wallet_listener_addr: &str, + socks_port: &str, + service_dirs: &Vec, +) -> Result<(), Error> { + let torrc_file_path = format!("{}{}{}", tor_config_directory, MAIN_SEPARATOR, TORRC_FILE); + + let tor_data_dir = format!("./{}", TOR_DATA_DIR); + + let mut props = TorRcConfig::new(); + props.add_item("SocksPort", socks_port); + props.add_item("DataDirectory", &tor_data_dir); + + for dir in service_dirs { + let service_file_name = format!("./{}{}{}", HIDDEN_SERVICES_DIR, MAIN_SEPARATOR, dir); + props.add_item("HiddenServiceDir", &service_file_name); + props.add_item("HiddenServicePort", &format!("80 {}", wallet_listener_addr)); + } + + props.write_to_file(&torrc_file_path)?; + + Ok(()) +} + +/// output entire tor config for a list of secret keys +pub fn output_tor_listener_config( + tor_config_directory: &str, + wallet_listener_addr: &str, + listener_keys: &Vec, +) -> Result<(), Error> { + let tor_data_dir = format!("{}{}{}", tor_config_directory, MAIN_SEPARATOR, TOR_DATA_DIR); + + // create data directory if it doesn't exist + fs::create_dir_all(&tor_data_dir).context(ErrorKind::IO)?; + + let mut service_dirs = vec![]; + + for k in listener_keys { + let service_dir = output_onion_service_config(tor_config_directory, &k)?; + service_dirs.push(service_dir); + } + + // hidden service listener doesn't need a socks port + output_torrc( + tor_config_directory, + wallet_listener_addr, + "0", + &service_dirs, + )?; + + Ok(()) +} + +/// output tor config for a send +pub fn output_tor_sender_config( + tor_config_dir: &str, + socks_listener_addr: &str, +) -> Result<(), Error> { + // create data directory if it doesn't exist + fs::create_dir_all(&tor_config_dir).context(ErrorKind::IO)?; + + output_torrc(tor_config_dir, "", socks_listener_addr, &vec![])?; + + Ok(()) +} + +/// Derive a secret key given a derivation path and index +pub fn address_derivation_path( + keychain: &K, + parent_key_id: &Identifier, + index: u32, +) -> Result +where + K: Keychain, +{ + let mut key_path = parent_key_id.to_path(); + // An output derivation for acct m/0 + // is m/0/0/0, m/0/0/1 (for instance), m/1 is m/1/0/0, m/1/0/1 + // Address generation path should be + // for m/0: m/0/1/0, m/0/1/1 + // for m/1: m/1/1/0, m/1/1/1 + key_path.path[1] = ChildNumber::from(1); + key_path.depth = key_path.depth + 1; + key_path.path[key_path.depth as usize - 1] = ChildNumber::from(index); + let key_id = Identifier::from_path(&key_path); + debug!("Onion Address derivation path is: {}", key_id); + let sec_key = keychain.derive_key(0, &key_id, &SwitchCommitmentType::None)?; + let hashed = blake2b(32, &[], &sec_key.0[..]); + Ok(SecretKey::from_slice( + &keychain.secp(), + &hashed.as_bytes()[..], + )?) +} + +pub fn is_tor_address(input: &str) -> Result<(), Error> { + let mut input = input.to_uppercase(); + if input.starts_with("HTTP://") || input.starts_with("HTTPS://") { + input = input.replace("HTTP://", ""); + input = input.replace("HTTPS://", ""); + } + if input.ends_with(".ONION") { + input = input.replace(".ONION", ""); + } + // for now, just check input is the right length and is base32 + if input.len() != 56 { + return Err(ErrorKind::NotOnion.to_owned())?; + } + let _ = BASE32 + .decode(input.as_bytes()) + .context(ErrorKind::NotOnion)?; + Ok(()) +} + +pub fn complete_tor_address(input: &str) -> Result { + let _ = is_tor_address(input)?; + let mut input = input.to_uppercase(); + if !input.starts_with("HTTP://") && !input.starts_with("HTTPS://") { + input = format!("HTTP://{}", input); + } + if !input.ends_with(".ONION") { + input = format!("{}.ONION", input); + } + Ok(input.to_lowercase()) +} + +#[cfg(test)] +mod tests { + use super::*; + + use rand::rngs::mock::StepRng; + use rand::thread_rng; + + use crate::util::{self, secp, static_secp_instance}; + + pub fn clean_output_dir(test_dir: &str) { + let _ = fs::remove_dir_all(test_dir); + } + + pub fn setup(test_dir: &str) { + util::init_test_logger(); + clean_output_dir(test_dir); + } + + #[test] + fn gen_ed25519_pub_key() -> Result<(), Error> { + let secp_inst = static_secp_instance(); + let secp = secp_inst.lock(); + let mut test_rng = StepRng::new(1234567890u64, 1); + let sec_key = secp::key::SecretKey::new(&secp, &mut test_rng); + println!("{:?}", sec_key); + let (_, d_pub_key) = ed25519_keypair(&sec_key)?; + println!("{:?}", d_pub_key); + // some randoms + for _ in 0..1000 { + let sec_key = secp::key::SecretKey::new(&secp, &mut thread_rng()); + let (_, _) = ed25519_keypair(&sec_key)?; + } + Ok(()) + } + + #[test] + fn gen_onion_address() -> Result<(), Error> { + let secp_inst = static_secp_instance(); + let secp = secp_inst.lock(); + let mut test_rng = StepRng::new(1234567890u64, 1); + let sec_key = secp::key::SecretKey::new(&secp, &mut test_rng); + println!("{:?}", sec_key); + let (_, d_pub_key) = ed25519_keypair(&sec_key)?; + let address = onion_address(&d_pub_key)?; + assert_eq!( + "kcgiy5g6m76nzlzz4vyqmgdv34f6yokdqwfhdhaafanpo5p4fceibyid", + address + ); + println!("{}", address); + Ok(()) + } + + #[test] + fn test_service_config() -> Result<(), Error> { + let test_dir = "target/test_output/onion_service"; + setup(test_dir); + let secp_inst = static_secp_instance(); + let secp = secp_inst.lock(); + let mut test_rng = StepRng::new(1234567890u64, 1); + let sec_key = secp::key::SecretKey::new(&secp, &mut test_rng); + output_onion_service_config(test_dir, &sec_key)?; + clean_output_dir(test_dir); + Ok(()) + } + + #[test] + fn test_output_tor_config() -> Result<(), Error> { + let test_dir = "./target/test_output/tor"; + setup(test_dir); + let secp_inst = static_secp_instance(); + let secp = secp_inst.lock(); + let mut test_rng = StepRng::new(1234567890u64, 1); + let sec_key = secp::key::SecretKey::new(&secp, &mut test_rng); + output_tor_listener_config(test_dir, "127.0.0.1:3415", &vec![sec_key])?; + clean_output_dir(test_dir); + Ok(()) + } + + #[test] + fn test_is_tor_address() -> Result<(), Error> { + assert!(is_tor_address("2a6at2obto3uvkpkitqp4wxcg6u36qf534eucbskqciturczzc5suyid").is_ok()); + assert!(is_tor_address( + "http://kcgiy5g6m76nzlzz4vyqmgdv34f6yokdqwfhdhaafanpo5p4fceibyid.onion" + ) + .is_ok()); + assert!(is_tor_address( + "https://kcgiy5g6m76nzlzz4vyqmgdv34f6yokdqwfhdhaafanpo5p4fceibyid.onion" + ) + .is_ok()); + assert!( + is_tor_address("http://kcgiy5g6m76nzlzz4vyqmgdv34f6yokdqwfhdhaafanpo5p4fceibyid") + .is_ok() + ); + assert!( + is_tor_address("kcgiy5g6m76nzlzz4vyqmgdv34f6yokdqwfhdhaafanpo5p4fceibyid.onion") + .is_ok() + ); + // address too short + assert!(is_tor_address( + "http://kcgiy5g6m76nzlz4vyqmgdv34f6yokdqwfhdhaafanpo5p4fceibyid.onion" + ) + .is_err()); + assert!(is_tor_address("kcgiy5g6m76nzlz4vyqmgdv34f6yokdqwfhdhaafanpo5p4fceibyid").is_err()); + Ok(()) + } + + #[test] + fn test_complete_tor_address() -> Result<(), Error> { + assert_eq!( + "http://2a6at2obto3uvkpkitqp4wxcg6u36qf534eucbskqciturczzc5suyid.onion", + complete_tor_address("2a6at2obto3uvkpkitqp4wxcg6u36qf534eucbskqciturczzc5suyid") + .unwrap() + ); + assert_eq!( + "http://2a6at2obto3uvkpkitqp4wxcg6u36qf534eucbskqciturczzc5suyid.onion", + complete_tor_address("http://2a6at2obto3uvkpkitqp4wxcg6u36qf534eucbskqciturczzc5suyid") + .unwrap() + ); + assert_eq!( + "http://2a6at2obto3uvkpkitqp4wxcg6u36qf534eucbskqciturczzc5suyid.onion", + complete_tor_address("2a6at2obto3uvkpkitqp4wxcg6u36qf534eucbskqciturczzc5suyid.onion") + .unwrap() + ); + assert!( + complete_tor_address("2a6at2obto3uvkpkitqp4wxcg6u36qf534eucbskqciturczzc5suyi") + .is_err() + ); + Ok(()) + } +} diff --git a/impls/src/tor/mod.rs b/impls/src/tor/mod.rs new file mode 100644 index 000000000..8a2b38702 --- /dev/null +++ b/impls/src/tor/mod.rs @@ -0,0 +1,16 @@ +// Copyright 2018 The Grin Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub mod config; +pub mod process; diff --git a/impls/src/tor/process.rs b/impls/src/tor/process.rs new file mode 100644 index 000000000..aa7dee6f6 --- /dev/null +++ b/impls/src/tor/process.rs @@ -0,0 +1,290 @@ +// Copyright 2019 The Grin Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// BSD 3-Clause License +// +// Copyright (c) 2016, Dhole +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +//! Tor process control +//! Derived from from from https://github.com/Dhole/rust-tor-controller.git + +extern crate chrono; +extern crate regex; +extern crate timer; + +use regex::Regex; +use std::fs::{self, File}; +use std::io; +use std::io::Write; +use std::io::{BufRead, BufReader}; +use std::path::{Path, MAIN_SEPARATOR}; +use std::process::{Child, ChildStdout, Command, Stdio}; +use std::sync::mpsc::channel; +use std::thread; +use sysinfo::{Process, ProcessExt, Signal}; + +#[cfg(windows)] +const TOR_EXE_NAME: &'static str = "tor.exe"; +#[cfg(not(windows))] +const TOR_EXE_NAME: &'static str = "tor"; + +#[derive(Debug)] +pub enum Error { + Process(String), + IO(io::Error), + PID(String), + Tor(String, Vec), + InvalidLogLine, + InvalidBootstrapLine(String), + Regex(regex::Error), + ProcessNotStarted, + Timeout, +} + +#[cfg(windows)] +fn get_process(pid: i32) -> Process { + Process::new(pid as usize, None, 0) +} + +#[cfg(not(windows))] +fn get_process(pid: i32) -> Process { + Process::new(pid, None, 0) +} + +pub struct TorProcess { + tor_cmd: String, + args: Vec, + torrc_path: Option, + completion_percent: u8, + timeout: u32, + working_dir: Option, + pub stdout: Option>, + pub process: Option, +} + +impl TorProcess { + pub fn new() -> Self { + TorProcess { + tor_cmd: TOR_EXE_NAME.to_string(), + args: vec![], + torrc_path: None, + completion_percent: 100 as u8, + timeout: 0 as u32, + working_dir: None, + stdout: None, + process: None, + } + } + + pub fn tor_cmd(&mut self, tor_cmd: &str) -> &mut Self { + self.tor_cmd = tor_cmd.to_string(); + self + } + + pub fn torrc_path(&mut self, torrc_path: &str) -> &mut Self { + self.torrc_path = Some(torrc_path.to_string()); + self + } + + pub fn arg(&mut self, arg: String) -> &mut Self { + self.args.push(arg); + self + } + + pub fn args(&mut self, args: Vec) -> &mut Self { + for arg in args { + self.arg(arg); + } + self + } + + pub fn completion_percent(&mut self, completion_percent: u8) -> &mut Self { + self.completion_percent = completion_percent; + self + } + + pub fn timeout(&mut self, timeout: u32) -> &mut Self { + self.timeout = timeout; + self + } + + pub fn working_dir(&mut self, dir: &str) -> &mut Self { + self.working_dir = Some(dir.to_string()); + self + } + + // The tor process will have its stdout piped, so if the stdout lines are not consumed they + // will keep accumulating over time, increasing the consumed memory. + pub fn launch(&mut self) -> Result<&mut Self, Error> { + let mut tor = Command::new(&self.tor_cmd); + + if let Some(ref d) = self.working_dir { + tor.current_dir(&d); + let pid_file_name = format!("{}{}pid", d, MAIN_SEPARATOR); + // kill off PID if its already running + if Path::new(&pid_file_name).exists() { + let pid = fs::read_to_string(&pid_file_name).map_err(|err| Error::IO(err))?; + let pid = pid + .parse::() + .map_err(|err| Error::PID(format!("{:?}", err)))?; + let process = get_process(pid); + let _ = process.kill(Signal::Kill); + } + } + if let Some(ref torrc_path) = self.torrc_path { + tor.args(&vec!["-f", torrc_path]); + } + let mut tor_process = tor + .args(&self.args) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .map_err(|err| { + let msg = format!("TOR executable (`{}`) not found. Please ensure TOR is installed and on the path: {:?}", TOR_EXE_NAME, err); + Error::Process(msg) + })?; + + if let Some(ref d) = self.working_dir { + // split out the process id, so if we don't exit cleanly + // we can take it down on the next run + let pid_file_name = format!("{}{}pid", d, MAIN_SEPARATOR); + let mut file = File::create(pid_file_name).map_err(|err| Error::IO(err))?; + file.write_all(format!("{}", tor_process.id()).as_bytes()) + .map_err(|err| Error::IO(err))?; + } + + let stdout = BufReader::new(tor_process.stdout.take().unwrap()); + + self.process = Some(tor_process); + let completion_percent = self.completion_percent; + + let (stdout_tx, stdout_rx) = channel(); + let stdout_timeout_tx = stdout_tx.clone(); + + let timer = timer::Timer::new(); + let _guard = + timer.schedule_with_delay(chrono::Duration::seconds(self.timeout as i64), move || { + stdout_timeout_tx.send(Err(Error::Timeout)).unwrap_or(()); + }); + let stdout_thread = thread::spawn(move || { + stdout_tx + .send(Self::parse_tor_stdout(stdout, completion_percent)) + .unwrap_or(()); + }); + match stdout_rx.recv().unwrap() { + Ok(stdout) => { + stdout_thread.join().unwrap(); + self.stdout = Some(stdout); + Ok(self) + } + Err(err) => { + self.kill().unwrap_or(()); + stdout_thread.join().unwrap(); + Err(err) + } + } + } + + fn parse_tor_stdout( + mut stdout: BufReader, + completion_perc: u8, + ) -> Result, Error> { + let re_bootstrap = Regex::new(r"^\[notice\] Bootstrapped (?P[0-9]+)%(.*): ") + .map_err(|err| Error::Regex(err))?; + + let timestamp_len = "May 16 02:50:08.792".len(); + let mut warnings = Vec::new(); + let mut raw_line = String::new(); + + while stdout + .read_line(&mut raw_line) + .map_err(|err| Error::Process(format!("{}", err)))? + > 0 + { + { + if raw_line.len() < timestamp_len + 1 { + return Err(Error::InvalidLogLine); + } + let timestamp = &raw_line[..timestamp_len]; + let line = &raw_line[timestamp_len + 1..raw_line.len() - 1]; + debug!("{} {}", timestamp, line); + match line.split(' ').nth(0) { + Some("[notice]") => { + if let Some("Bootstrapped") = line.split(' ').nth(1) { + let perc = re_bootstrap + .captures(line) + .and_then(|c| c.name("perc")) + .and_then(|pc| pc.as_str().parse::().ok()) + .ok_or(Error::InvalidBootstrapLine(line.to_string()))?; + + if perc >= completion_perc { + break; + } + } + } + Some("[warn]") => warnings.push(line.to_string()), + Some("[err]") => return Err(Error::Tor(line.to_string(), warnings)), + _ => (), + } + } + raw_line.clear(); + } + Ok(stdout) + } + + pub fn kill(&mut self) -> Result<(), Error> { + if let Some(ref mut process) = self.process { + Ok(process + .kill() + .map_err(|err| Error::Process(format!("{}", err)))?) + } else { + Err(Error::ProcessNotStarted) + } + } +} + +impl Drop for TorProcess { + // kill the child + fn drop(&mut self) { + trace!("DROPPING TOR PROCESS"); + self.kill().unwrap_or(()); + } +} diff --git a/libwallet/src/error.rs b/libwallet/src/error.rs index c8318026b..a4366f0f7 100644 --- a/libwallet/src/error.rs +++ b/libwallet/src/error.rs @@ -213,6 +213,14 @@ pub enum ErrorKind { #[fail(display = "Supplied Keychain Mask Token is incorrect")] InvalidKeychainMask, + /// Tor Process error + #[fail(display = "Tor Process Error: {}", _0)] + TorProcess(String), + + /// Tor Configuration Error + #[fail(display = "Tor Config Error: {}", _0)] + TorConfig(String), + /// Other #[fail(display = "Generic error: {}", _0)] GenericError(String), diff --git a/libwallet/src/types.rs b/libwallet/src/types.rs index 293fdb3fa..3ff85df5b 100644 --- a/libwallet/src/types.rs +++ b/libwallet/src/types.rs @@ -15,7 +15,7 @@ //! Types and traits that should be provided by a wallet //! implementation -use crate::config::WalletConfig; +use crate::config::{TorConfig, WalletConfig}; use crate::error::{Error, ErrorKind}; use crate::grin_core::core::hash::Hash; use crate::grin_core::core::{Output, Transaction, TxKernel}; @@ -66,6 +66,7 @@ where file_name: &str, wallet_config: Option, logging_config: Option, + tor_config: Option, ) -> Result<(), Error>; /// diff --git a/src/bin/grin-wallet.yml b/src/bin/grin-wallet.yml index d63fb5263..175712b23 100644 --- a/src/bin/grin-wallet.yml +++ b/src/bin/grin-wallet.yml @@ -68,6 +68,11 @@ subcommands: - keybase default_value: http takes_value: true + - no_tor: + help: Don't start TOR listener when starting HTTP listener + short: n + long: no_tor + takes_value: false - owner_api: about: Runs the wallet's local web API args: diff --git a/src/cmd/wallet.rs b/src/cmd/wallet.rs index 8b5d016e4..1c29c98e6 100644 --- a/src/cmd/wallet.rs +++ b/src/cmd/wallet.rs @@ -31,7 +31,9 @@ where C: NodeClient + 'static, { // just get defaults from the global config - let wallet_config = config.members.unwrap().wallet; + let wallet_config = config.members.clone().unwrap().wallet; + + let tor_config = config.members.unwrap().tor; // Check the node version info, and exit with report if we're not compatible //let mut node_client = HTTPNodeClient::new(&wallet_config.check_node_api_http_addr, None); @@ -57,7 +59,14 @@ where } // ... if node isn't available, allow offline functions - let res = wallet_args::wallet_command(wallet_args, wallet_config, node_client, false, |_| {}); + let res = wallet_args::wallet_command( + wallet_args, + wallet_config, + tor_config, + node_client, + false, + |_| {}, + ); // we need to give log output a chance to catch up before exiting thread::sleep(Duration::from_millis(100)); diff --git a/src/cmd/wallet_args.rs b/src/cmd/wallet_args.rs index 193be7b53..11bb9d304 100644 --- a/src/cmd/wallet_args.rs +++ b/src/cmd/wallet_args.rs @@ -19,9 +19,10 @@ use crate::util::{Mutex, ZeroingString}; /// Argument parsing and error handling for wallet commands use clap::ArgMatches; use failure::Fail; -use grin_wallet_config::WalletConfig; +use grin_wallet_config::{TorConfig, WalletConfig}; use grin_wallet_controller::command; use grin_wallet_controller::{Error, ErrorKind}; +use grin_wallet_impls::tor::config::is_tor_address; use grin_wallet_impls::{DefaultLCProvider, DefaultWalletImpl}; use grin_wallet_impls::{PathToSlate, SlateGetter as _}; use grin_wallet_libwallet::Slate; @@ -387,12 +388,16 @@ where pub fn parse_listen_args( config: &mut WalletConfig, + tor_config: &mut TorConfig, args: &ArgMatches, ) -> Result { if let Some(port) = args.value_of("port") { config.api_listen_port = port.parse().unwrap(); } let method = parse_required(args, "method")?; + if args.is_present("no_tor") { + tor_config.use_tor_listener = false; + } Ok(command::ListenArgs { method: method.to_owned(), }) @@ -468,10 +473,12 @@ pub fn parse_send_args(args: &ArgMatches) -> Result Result( wallet_args: &ArgMatches, mut wallet_config: WalletConfig, + tor_config: Option, mut node_client: C, test_mode: bool, wallet_inst_cb: F, @@ -820,6 +828,17 @@ where wallet_config.data_file_dir = top_level_wallet_dir.to_str().unwrap().into(); } + // for backwards compatibility: If tor config doesn't exist in the file, assume + // the top level directory for data + let tor_config = match tor_config { + Some(tc) => tc, + None => { + let mut tc = TorConfig::default(); + tc.send_config_dir = wallet_config.data_file_dir.clone(); + tc + } + }; + // Instantiate wallet (doesn't open the wallet) let wallet = inst_wallet::, C, keychain::ExtKeychain>( @@ -896,11 +915,13 @@ where } ("listen", Some(args)) => { let mut c = wallet_config.clone(); - let a = arg_parse!(parse_listen_args(&mut c, &args)); + let mut t = tor_config.clone(); + let a = arg_parse!(parse_listen_args(&mut c, &mut t, &args)); command::listen( wallet, Arc::new(Mutex::new(keychain_mask)), &c, + &t, &a, &global_wallet_args.clone(), ) @@ -924,6 +945,7 @@ where command::send( wallet, km, + Some(tor_config), a, wallet_config.dark_background_color_scheme.unwrap_or(true), ) @@ -945,6 +967,7 @@ where command::process_invoice( wallet, km, + Some(tor_config), a, wallet_config.dark_background_color_scheme.unwrap_or(true), ) diff --git a/tests/cmd_line_basic.rs b/tests/cmd_line_basic.rs index bb933b26b..464434578 100644 --- a/tests/cmd_line_basic.rs +++ b/tests/cmd_line_basic.rs @@ -60,8 +60,13 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: // add wallet to proxy //let wallet1 = test_framework::create_wallet(&format!("{}/wallet1", test_dir), client1.clone()); let config1 = initial_setup_wallet(test_dir, "wallet1"); - let (wallet1, mask1_i) = - instantiate_wallet(config1.clone(), client1.clone(), "password", "default")?; + let wallet_config1 = config1.clone().members.unwrap().wallet; + let (wallet1, mask1_i) = instantiate_wallet( + wallet_config1.clone(), + client1.clone(), + "password", + "default", + )?; wallet_proxy.add_wallet( "wallet1", client1.get_send_instance(), @@ -74,8 +79,13 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: execute_command(&app, test_dir, "wallet2", &client2, arg_vec.clone())?; let config2 = initial_setup_wallet(test_dir, "wallet2"); - let (wallet2, mask2_i) = - instantiate_wallet(config2.clone(), client2.clone(), "password", "default")?; + let wallet_config2 = config2.clone().members.unwrap().wallet; + let (wallet2, mask2_i) = instantiate_wallet( + wallet_config2.clone(), + client2.clone(), + "password", + "default", + )?; wallet_proxy.add_wallet( "wallet2", client2.get_send_instance(), @@ -137,8 +147,9 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: // Mine a bit into wallet 1 so we have something to send // (TODO: Be able to stop listeners so we can test this better) + let wallet_config1 = config1.clone().members.unwrap().wallet; let (wallet1, mask1_i) = - instantiate_wallet(config1.clone(), client1.clone(), "password", "default")?; + instantiate_wallet(wallet_config1, client1.clone(), "password", "default")?; let mask1 = (&mask1_i).as_ref(); grin_wallet_controller::controller::owner_single_use(wallet1.clone(), mask1, |api, m| { api.set_active_account(m, "mining")?; @@ -211,8 +222,13 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?; bh += 1; - let (wallet1, mask1_i) = - instantiate_wallet(config1.clone(), client1.clone(), "password", "default")?; + let wallet_config1 = config1.clone().members.unwrap().wallet; + let (wallet1, mask1_i) = instantiate_wallet( + wallet_config1.clone(), + client1.clone(), + "password", + "default", + )?; let mask1 = (&mask1_i).as_ref(); // Check our transaction log, should have 10 entries @@ -238,8 +254,13 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: execute_command(&app, test_dir, "wallet2", &client1, arg_vec)?; // check results in wallet 2 - let (wallet2, mask2_i) = - instantiate_wallet(config2.clone(), client2.clone(), "password", "default")?; + let wallet_config2 = config2.clone().members.unwrap().wallet; + let (wallet2, mask2_i) = instantiate_wallet( + wallet_config2.clone(), + client2.clone(), + "password", + "default", + )?; let mask2 = (&mask2_i).as_ref(); grin_wallet_controller::controller::owner_single_use(wallet2.clone(), mask2, |api, m| { @@ -296,8 +317,13 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: bh += 1; // Check our transaction log, should have bh entries + one for the self receive - let (wallet1, mask1_i) = - instantiate_wallet(config1.clone(), client1.clone(), "password", "default")?; + let wallet_config1 = config1.clone().members.unwrap().wallet; + let (wallet1, mask1_i) = instantiate_wallet( + wallet_config1.clone(), + client1.clone(), + "password", + "default", + )?; let mask1 = (&mask1_i).as_ref(); grin_wallet_controller::controller::owner_single_use(wallet1.clone(), mask1, |api, m| { @@ -332,8 +358,13 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: bh += 1; // Check our transaction log, should have bh entries + 2 for the self receives - let (wallet1, mask1_i) = - instantiate_wallet(config1.clone(), client1.clone(), "password", "default")?; + let wallet_config1 = config1.clone().members.unwrap().wallet; + let (wallet1, mask1_i) = instantiate_wallet( + wallet_config1.clone(), + client1.clone(), + "password", + "default", + )?; let mask1 = (&mask1_i).as_ref(); grin_wallet_controller::controller::owner_single_use(wallet1.clone(), mask1, |api, m| { diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 729fc0c68..d1333123a 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -63,13 +63,23 @@ macro_rules! setup_proxy { let arg_vec = vec!["grin-wallet", "-p", "password", "init", "-h"]; // should create new wallet file let $client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone()); - execute_command(&app, $test_dir, "wallet1", &$client1, arg_vec.clone())?; + + let target = std::path::PathBuf::from(format!("{}/wallet1/grin-wallet.toml", $test_dir)); + println!("{:?}", target); + if !target.exists() { + execute_command(&app, $test_dir, "wallet1", &$client1, arg_vec.clone())?; + } // add wallet to proxy let config1 = initial_setup_wallet($test_dir, "wallet1"); + let wallet_config1 = config1.clone().members.unwrap().wallet; //config1.owner_api_listen_port = Some(13420); - let ($wallet1, mask1_i) = - instantiate_wallet(config1.clone(), $client1.clone(), "password", "default")?; + let ($wallet1, mask1_i) = instantiate_wallet( + wallet_config1.clone(), + $client1.clone(), + "password", + "default", + )?; let $mask1 = (&mask1_i).as_ref(); wallet_proxy.add_wallet( "wallet1", @@ -80,12 +90,21 @@ macro_rules! setup_proxy { // Create wallet 2, which will run a listener let $client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone()); - execute_command(&app, $test_dir, "wallet2", &$client2, arg_vec.clone())?; + + let target = std::path::PathBuf::from(format!("{}/wallet2/grin-wallet.toml", $test_dir)); + if !target.exists() { + execute_command(&app, $test_dir, "wallet2", &$client2, arg_vec.clone())?; + } let config2 = initial_setup_wallet($test_dir, "wallet2"); + let wallet_config2 = config2.clone().members.unwrap().wallet; //config2.api_listen_port = 23415; - let ($wallet2, mask2_i) = - instantiate_wallet(config2.clone(), $client2.clone(), "password", "default")?; + let ($wallet2, mask2_i) = instantiate_wallet( + wallet_config2.clone(), + $client2.clone(), + "password", + "default", + )?; let $mask2 = (&mask2_i).as_ref(); wallet_proxy.add_wallet( "wallet2", @@ -150,7 +169,7 @@ pub fn config_command_wallet( /// Handles setup and detection of paths for wallet #[allow(dead_code)] -pub fn initial_setup_wallet(dir_name: &str, wallet_name: &str) -> WalletConfig { +pub fn initial_setup_wallet(dir_name: &str, wallet_name: &str) -> GlobalWalletConfig { let mut current_dir; current_dir = env::current_dir().unwrap_or_else(|e| { panic!("Error creating config file: {}", e); @@ -160,11 +179,7 @@ pub fn initial_setup_wallet(dir_name: &str, wallet_name: &str) -> WalletConfig { let _ = fs::create_dir_all(current_dir.clone()); let mut config_file_name = current_dir.clone(); config_file_name.push("grin-wallet.toml"); - GlobalWalletConfig::new(config_file_name.to_str().unwrap()) - .unwrap() - .members - .unwrap() - .wallet + GlobalWalletConfig::new(config_file_name.to_str().unwrap()).unwrap() } fn get_wallet_subcommand<'a>( @@ -247,10 +262,19 @@ pub fn execute_command( ) -> Result { let args = app.clone().get_matches_from(arg_vec); let _ = get_wallet_subcommand(test_dir, wallet_name, args.clone()); - let mut config = initial_setup_wallet(test_dir, wallet_name); + let config = initial_setup_wallet(test_dir, wallet_name); + let mut wallet_config = config.clone().members.unwrap().wallet; + let tor_config = config.clone().members.unwrap().tor; //unset chain type so it doesn't get reset - config.chain_type = None; - wallet_args::wallet_command(&args, config.clone(), client.clone(), true, |_| {}) + wallet_config.chain_type = None; + wallet_args::wallet_command( + &args, + wallet_config.clone(), + tor_config, + client.clone(), + true, + |_| {}, + ) } // as above, but without necessarily setting up the wallet @@ -283,11 +307,12 @@ where let args = app.clone().get_matches_from(arg_vec); let _ = get_wallet_subcommand(test_dir, wallet_name, args.clone()); let config = config::initial_setup_wallet(&ChainTypes::AutomatedTesting, None).unwrap(); - let mut wallet_config = config.members.unwrap().wallet.clone(); + let mut wallet_config = config.clone().members.unwrap().wallet; wallet_config.chain_type = None; wallet_config.api_secret_path = None; wallet_config.node_api_secret_path = None; - wallet_args::wallet_command(&args, wallet_config, client.clone(), true, f) + let tor_config = config.members.unwrap().tor.clone(); + wallet_args::wallet_command(&args, wallet_config, tor_config, client.clone(), true, f) } pub fn post(url: &Url, api_secret: Option, input: &IN) -> Result diff --git a/tests/data/v3_reqs/create_config.req.json b/tests/data/v3_reqs/create_config.req.json index f88b97af4..fdd11e707 100644 --- a/tests/data/v3_reqs/create_config.req.json +++ b/tests/data/v3_reqs/create_config.req.json @@ -4,7 +4,8 @@ "params": { "chain_type": "AutomatedTesting", "wallet_config": null, - "logging_config": null + "logging_config": null, + "tor_config": null }, "id": 1 } diff --git a/tests/owner_v2_sanity.rs b/tests/owner_v2_sanity.rs index 3ad2334e8..0955ac523 100644 --- a/tests/owner_v2_sanity.rs +++ b/tests/owner_v2_sanity.rs @@ -59,7 +59,15 @@ fn owner_v2_sanity() -> Result<(), grin_wallet_controller::Error> { }); // run the foreign listener for wallet 2 - let arg_vec = vec!["grin-wallet", "-p", "password", "listen", "-l", "23415"]; + let arg_vec = vec![ + "grin-wallet", + "-p", + "password", + "listen", + "-l", + "23415", + "-n", + ]; // Set owner listener running thread::spawn(move || { let yml = load_yaml!("../src/bin/grin-wallet.yml"); diff --git a/tests/owner_v3_lifecycle.rs b/tests/owner_v3_lifecycle.rs index ad5c627ba..5892eb633 100644 --- a/tests/owner_v3_lifecycle.rs +++ b/tests/owner_v3_lifecycle.rs @@ -72,9 +72,14 @@ fn owner_v3_lifecycle() -> Result<(), grin_wallet_controller::Error> { execute_command(&app, test_dir, "wallet2", &client2, arg_vec.clone())?; let config2 = initial_setup_wallet(test_dir, "wallet2"); + let wallet_config2 = config2.clone().members.unwrap().wallet; //config2.api_listen_port = 23415; - let (wallet2, mask2_i) = - instantiate_wallet(config2.clone(), client2.clone(), "password", "default")?; + let (wallet2, mask2_i) = instantiate_wallet( + wallet_config2.clone(), + client2.clone(), + "password", + "default", + )?; wallet_proxy.add_wallet( "wallet2", client2.get_send_instance(), diff --git a/tests/tor_dev_helper.rs b/tests/tor_dev_helper.rs new file mode 100644 index 000000000..dea9ef8ee --- /dev/null +++ b/tests/tor_dev_helper.rs @@ -0,0 +1,101 @@ +// Copyright 2019 The Grin Developers +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[macro_use] +extern crate clap; + +#[macro_use] +extern crate log; + +extern crate grin_wallet; + +use grin_wallet_impls::test_framework::{self, LocalWalletClient, WalletProxy}; +use grin_wallet_util::grin_core::global::{self, ChainTypes}; + +use clap::App; +use std::thread; +use std::time::Duration; + +use grin_wallet_impls::DefaultLCProvider; +use grin_wallet_util::grin_keychain::ExtKeychain; + +use grin_wallet_util::grin_util as util; + +#[macro_use] +mod common; +use common::{execute_command, initial_setup_wallet, instantiate_wallet}; + +// Development testing helper for tor/socks investigation. +// Not (yet) to be run as part of automated testing + +fn setup_no_clean() { + util::init_test_logger(); + global::set_mining_mode(ChainTypes::AutomatedTesting); +} + +#[ignore] +#[test] +fn socks_tor() -> Result<(), grin_wallet_controller::Error> { + let test_dir = "target/test_output/socks_tor"; + let yml = load_yaml!("../src/bin/grin-wallet.yml"); + let app = App::from_yaml(yml); + setup_no_clean(); + + setup_proxy!(test_dir, chain, wallet1, client1, mask1, wallet2, client2, _mask2); + + // Tor should be running at this point for wallet 2, with a hidden service + // bound to the listening port 53415. By default, tor will also be running + // a socks proxy lister at 127.0.0.1 9050 (both wallets can use for now) + // + // Relevant torrc config: + // HiddenServiceDir ./hidden_service/ + // HiddenServicePort 80 127.0.0.1:53415 + // + // tor -f torrc + + // Substitute whatever onion address has been created + let onion_address = "2a6at2obto3uvkpkitqp4wxcg6u36qf534eucbskqciturczzc5suyid"; + + // run the foreign listener for wallet 2 + let arg_vec = vec!["grin-wallet", "-p", "password", "listen"]; + // Set owner listener running + thread::spawn(move || { + let yml = load_yaml!("../src/bin/grin-wallet.yml"); + let app = App::from_yaml(yml); + execute_command(&app, test_dir, "wallet2", &client2, arg_vec.clone()).unwrap(); + }); + + // dumb pause for now, hidden service should already be running + thread::sleep(Duration::from_millis(3000)); + + // mine into wallet 1 a bit + let bh = 5u64; + let _ = + test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false); + + // now, test send from wallet 1 over tor + let arg_vec = vec![ + "grin-wallet", + "-p", + "password", + "send", + "-c", + "2", + "-d", + onion_address, + "10", + ]; + execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?; + + Ok(()) +} diff --git a/util/Cargo.toml b/util/Cargo.toml index 2c2495c86..657299263 100644 --- a/util/Cargo.toml +++ b/util/Cargo.toml @@ -25,6 +25,7 @@ dirs = "1.0.3" # grin_store = "2.0.0" # For beta release + # grin_core = { git = "https://github.com/mimblewimble/grin", tag = "v2.1.0-beta.3"} # grin_keychain = { git = "https://github.com/mimblewimble/grin", tag = "v2.1.0-beta.3" } # grin_chain = { git = "https://github.com/mimblewimble/grin", tag = "v2.1.0-beta.3" }