Make a zcashd-wallet-tool executable.

Signed-off-by: Daira Hopwood <daira@jacaranda.org>
This commit is contained in:
Daira Hopwood 2022-03-01 09:24:12 +00:00
parent 13351ff31d
commit a36fceca70
8 changed files with 846 additions and 39 deletions

1
.gitignore vendored
View File

@ -3,6 +3,7 @@
*.exe
src/bitcoin
src/zcashd
src/zcashd-wallet-tool
src/zcash-cli
src/zcash-gtest
src/zcash-tx

299
Cargo.lock generated
View File

@ -2,6 +2,21 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "addr2line"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aead"
version = "0.4.3"
@ -17,7 +32,7 @@ version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8"
dependencies = [
"cfg-if",
"cfg-if 1.0.0",
"cipher",
"cpufeatures",
"opaque-debug",
@ -85,6 +100,21 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "backtrace"
version = "0.3.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e121dee8023ce33ab248d9ce1493df03c3b38a659b240096fcbd7048ff9c31f"
dependencies = [
"addr2line",
"cc",
"cfg-if 1.0.0",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[package]]
name = "base64ct"
version = "1.0.1"
@ -136,7 +166,7 @@ checksum = "d0830ae4cc96b0617cc912970c2b17e89456fecbf55e8eed53a956f37ab50c41"
dependencies = [
"hmac",
"pbkdf2",
"rand",
"rand 0.8.5",
"sha2",
"unicode-normalization",
"zeroize",
@ -284,6 +314,12 @@ version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "cfg-if"
version = "1.0.0"
@ -296,7 +332,7 @@ version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01b72a433d0cf2aef113ba70f62634c56fddb0f244e6377185c56a7cadbd8f91"
dependencies = [
"cfg-if",
"cfg-if 1.0.0",
"cipher",
"cpufeatures",
"zeroize",
@ -324,6 +360,19 @@ dependencies = [
"generic-array",
]
[[package]]
name = "clearscreen"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7ed49b0e894fe6264a58496c7ec4e9d3c46f66b59efae527cd5bee429d0a418"
dependencies = [
"nix",
"terminfo",
"thiserror",
"which",
"winapi",
]
[[package]]
name = "constant_time_eq"
version = "0.1.5"
@ -345,7 +394,7 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa"
dependencies = [
"cfg-if",
"cfg-if 1.0.0",
"crossbeam-utils",
]
@ -355,7 +404,7 @@ version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e"
dependencies = [
"cfg-if",
"cfg-if 1.0.0",
"crossbeam-epoch",
"crossbeam-utils",
]
@ -366,7 +415,7 @@ version = "0.9.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c00d6d2ea26e8b151d99093005cb442fb9a37aeaca582a03ec70946f49ab5ed9"
dependencies = [
"cfg-if",
"cfg-if 1.0.0",
"crossbeam-utils",
"lazy_static",
"memoffset",
@ -379,7 +428,7 @@ version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6"
dependencies = [
"cfg-if",
"cfg-if 1.0.0",
"lazy_static",
]
@ -428,7 +477,7 @@ version = "4.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c"
dependencies = [
"cfg-if",
"cfg-if 1.0.0",
"num_cpus",
]
@ -460,6 +509,16 @@ dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3"
dependencies = [
"cfg-if 0.1.10",
"dirs-sys",
]
[[package]]
name = "dirs-sys"
version = "0.3.6"
@ -601,7 +660,7 @@ version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
dependencies = [
"cfg-if",
"cfg-if 1.0.0",
"libc",
"wasi 0.9.0+wasi-snapshot-preview1",
]
@ -612,11 +671,17 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77"
dependencies = [
"cfg-if",
"cfg-if 1.0.0",
"libc",
"wasi 0.10.2+wasi-snapshot-preview1",
]
[[package]]
name = "gimli"
version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4"
[[package]]
name = "group"
version = "0.11.0"
@ -629,6 +694,26 @@ dependencies = [
"subtle",
]
[[package]]
name = "gumdrop"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46571f5d540478cf70d2a42dd0d6d8e9f4b9cc7531544b93311e657b86568a0b"
dependencies = [
"gumdrop_derive",
]
[[package]]
name = "gumdrop_derive"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "915ef07c710d84733522461de2a734d4d62a3fd39a4d4f404c2f385ef8618d05"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "halo2"
version = "0.1.0-beta.1"
@ -639,7 +724,7 @@ dependencies = [
"ff",
"group",
"pasta_curves",
"rand",
"rand 0.8.5",
"rayon",
]
@ -770,7 +855,7 @@ version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
"cfg-if 1.0.0",
]
[[package]]
@ -836,13 +921,16 @@ checksum = "33a33a362ce288760ec6a508b94caaec573ae7d3bbbd91b87aa0bad4456839db"
name = "librustzcash"
version = "0.2.0"
dependencies = [
"backtrace",
"bellman",
"blake2b_simd 1.0.0",
"blake2s_simd 1.0.0",
"bls12_381",
"byteorder",
"clearscreen",
"ed25519-zebra",
"group",
"gumdrop",
"hyper",
"incrementalmerkletree",
"ipnet",
@ -853,10 +941,12 @@ dependencies = [
"metrics-exporter-prometheus",
"nonempty",
"orchard",
"rand 0.8.5",
"rand_core 0.6.3",
"secp256k1",
"subtle",
"thiserror",
"time",
"tokio",
"tracing",
"tracing-appender",
@ -885,7 +975,7 @@ version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if",
"cfg-if 1.0.0",
]
[[package]]
@ -992,6 +1082,16 @@ dependencies = [
"sketches-ddsketch",
]
[[package]]
name = "miniz_oxide"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b"
dependencies = [
"adler",
"autocfg",
]
[[package]]
name = "mio"
version = "0.8.0"
@ -1023,6 +1123,29 @@ dependencies = [
"smallvec",
]
[[package]]
name = "nix"
version = "0.22.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4916f159ed8e5de0082076562152a76b7a1f64a01fd9d1e0fea002c37624faf"
dependencies = [
"bitflags",
"cc",
"cfg-if 1.0.0",
"libc",
"memoffset",
]
[[package]]
name = "nom"
version = "5.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af"
dependencies = [
"memchr",
"version_check",
]
[[package]]
name = "nonempty"
version = "0.7.0"
@ -1087,6 +1210,15 @@ dependencies = [
"libc",
]
[[package]]
name = "object"
version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.9.0"
@ -1119,7 +1251,7 @@ dependencies = [
"memuse",
"nonempty",
"pasta_curves",
"rand",
"rand 0.8.5",
"reddsa",
"serde",
"subtle",
@ -1161,7 +1293,7 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
dependencies = [
"cfg-if",
"cfg-if 1.0.0",
"instant",
"libc",
"redox_syscall",
@ -1190,7 +1322,7 @@ dependencies = [
"ff",
"group",
"lazy_static",
"rand",
"rand 0.8.5",
"static_assertions",
"subtle",
]
@ -1205,6 +1337,44 @@ dependencies = [
"password-hash",
]
[[package]]
name = "phf"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
dependencies = [
"phf_shared",
]
[[package]]
name = "phf_codegen"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815"
dependencies = [
"phf_generator",
"phf_shared",
]
[[package]]
name = "phf_generator"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526"
dependencies = [
"phf_shared",
"rand 0.7.3",
]
[[package]]
name = "phf_shared"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7"
dependencies = [
"siphasher",
]
[[package]]
name = "pin-project"
version = "1.0.10"
@ -1304,6 +1474,20 @@ dependencies = [
"nibble_vec",
]
[[package]]
name = "rand"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
dependencies = [
"getrandom 0.1.16",
"libc",
"rand_chacha 0.2.2",
"rand_core 0.5.1",
"rand_hc",
"rand_pcg",
]
[[package]]
name = "rand"
version = "0.8.5"
@ -1311,10 +1495,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_chacha 0.3.1",
"rand_core 0.6.3",
]
[[package]]
name = "rand_chacha"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
dependencies = [
"ppv-lite86",
"rand_core 0.5.1",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
@ -1343,6 +1537,24 @@ dependencies = [
"getrandom 0.2.5",
]
[[package]]
name = "rand_hc"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
dependencies = [
"rand_core 0.5.1",
]
[[package]]
name = "rand_pcg"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429"
dependencies = [
"rand_core 0.5.1",
]
[[package]]
name = "raw-cpuid"
version = "10.2.0"
@ -1464,6 +1676,12 @@ dependencies = [
"digest 0.10.3",
]
[[package]]
name = "rustc-demangle"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
[[package]]
name = "scopeguard"
version = "1.1.0"
@ -1515,7 +1733,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800"
dependencies = [
"block-buffer 0.9.0",
"cfg-if",
"cfg-if 1.0.0",
"cpufeatures",
"digest 0.9.0",
"opaque-debug",
@ -1530,6 +1748,12 @@ dependencies = [
"lazy_static",
]
[[package]]
name = "siphasher"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a86232ab60fa71287d7f2ddae4a7073f6b7aac33631c3015abb556f08c6d0a3e"
[[package]]
name = "sketches-ddsketch"
version = "0.1.2"
@ -1548,7 +1772,7 @@ version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e"
dependencies = [
"cfg-if",
"cfg-if 1.0.0",
"libc",
"winapi",
]
@ -1610,6 +1834,19 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "terminfo"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76971977e6121664ec1b960d1313aacfa75642adc93b9d4d53b247bd4cb1747e"
dependencies = [
"dirs",
"fnv",
"nom",
"phf",
"phf_codegen",
]
[[package]]
name = "thiserror"
version = "1.0.30"
@ -1648,8 +1885,15 @@ dependencies = [
"itoa 1.0.1",
"libc",
"num_threads",
"time-macros",
]
[[package]]
name = "time-macros"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6"
[[package]]
name = "tinyvec"
version = "1.5.1"
@ -1702,7 +1946,7 @@ version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6c650a8ef0cd2dd93736f033d21cbd1224c5a967aa0c258d00fcf7dafef9b9f"
dependencies = [
"cfg-if",
"cfg-if 1.0.0",
"pin-project-lite",
"tracing-attributes",
"tracing-core",
@ -1840,7 +2084,7 @@ version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06"
dependencies = [
"cfg-if",
"cfg-if 1.0.0",
"wasm-bindgen-macro",
]
@ -1898,6 +2142,17 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "which"
version = "4.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a5a7e487e921cf220206864a94a89b6c6905bfc19f1057fa26a4cb360e5c1d2"
dependencies = [
"either",
"lazy_static",
"libc",
]
[[package]]
name = "winapi"
version = "0.3.9"
@ -1996,7 +2251,7 @@ dependencies = [
"memuse",
"nonempty",
"orchard",
"rand",
"rand 0.8.5",
"rand_core 0.6.3",
"ripemd",
"secp256k1",

View File

@ -25,6 +25,10 @@ name = "rustzcash"
path = "src/rust/src/rustzcash.rs"
crate-type = ["staticlib"]
[[bin]]
name = "zcashd-wallet-tool"
path = "src/rust/src/wallet_tool.rs"
[dependencies]
bellman = "0.11"
blake2b_simd = "1"
@ -60,6 +64,13 @@ metrics-exporter-prometheus = "0.6"
thiserror = "1"
tokio = { version = "1.0", features = ["rt", "net", "time", "macros"] }
# Wallet tool
backtrace = "0.3"
clearscreen = "1.0"
gumdrop = "0.8"
rand = "0.8"
time = { version = "0.3", features = ["formatting", "macros"] }
[dependencies.tracing-subscriber]
version = "0.3"
default-features = false

View File

@ -19,17 +19,17 @@ Zcashd release.
Following the upgrade to 4.5.2, Zcashd will require that the user confirm that
they have backed up their new emergency recovery phrase, which may be obtained
from the output of the `z_exportwallet` RPC call. This confirmation can be
performed manually using the `zcashd-wallet-tool` utility that is supplied with
this release. The wallet will not allow the generation of new addresses until
this confirmation has been performed. It is recommended that after this
upgrade, that funds tied to preexisting addresses be migrated to newly
generated addresses so that all wallet funds are recoverable using the
emergency recovery phrase going forward. If you choose not to migrate funds in
this fashion, you will continue to need to securely back up the entire
`wallet.dat` file to ensure that you do not lose access to existing funds;
EXISTING FUNDS WILL NOT BE RECOVERABLE USING THE EMERGENCY RECOVERY PHRASE
UNLESS THEY HAVE BEEN MOVED TO A NEWLY GENERATED ADDRESS FOLLOWING THE 4.5.2
UPGRADE.
performed manually using the `zcashd-wallet-tool` utility that is supplied
with this release (built or installed in the same directory as `zcashd`).
The wallet will not allow the generation of new addresses until this
confirmation has been performed. It is recommended that after this upgrade,
that funds tied to preexisting addresses be migrated to newly generated
addresses so that all wallet funds are recoverable using the emergency
recovery phrase going forward. If you choose not to migrate funds in this
fashion, you will continue to need to securely back up the entire `wallet.dat`
file to ensure that you do not lose access to existing funds; EXISTING FUNDS
WILL NOT BE RECOVERABLE USING THE EMERGENCY RECOVERY PHRASE UNLESS THEY HAVE
BEEN MOVED TO A NEWLY GENERATED ADDRESS FOLLOWING THE 4.5.2 UPGRADE.
New RPC Methods
---------------

View File

@ -29,6 +29,9 @@ LIBSECP256K1=secp256k1/libsecp256k1.la
LIBUNIVALUE=univalue/libunivalue.la
LIBZCASH=libzcash.a
WALLET_TOOL_BIN=zcashd-wallet-tool$(EXEEXT)
WALLET_TOOL_BUILD=$(top_builddir)/target/$(RUST_TARGET)/release/zcashd-wallet-tool$(EXEEXT)
if ENABLE_ZMQ
LIBBITCOIN_ZMQ=libbitcoin_zmq.a
endif
@ -47,7 +50,7 @@ endif
# - Note that this does not prevent the secp256k1-sys vendored code from being built; this
# requires https://github.com/rust-bitcoin/rust-secp256k1/issues/380 to be addressed.
RUST_ENV_VARS = RUSTC="$(RUSTC)" TERM=dumb RUSTFLAGS="--cfg=rust_secp_no_symbol_renaming"
RUST_BUILD_OPTS = --lib --release --target $(RUST_TARGET)
RUST_BUILD_OPTS = --release --target $(RUST_TARGET) --manifest-path $(top_srcdir)/Cargo.toml
rust_verbose = $(rust_verbose_@AM_V@)
rust_verbose_ = $(rust_verbose_@AM_DEFAULT_V@)
@ -72,10 +75,16 @@ $(CARGO_CONFIGURED): $(top_srcdir)/.cargo/config.offline
$(AM_V_at)touch $@
endif
cargo-build: $(CARGO_CONFIGURED)
$(RUST_ENV_VARS) $(CARGO) build $(RUST_BUILD_OPTS) $(rust_verbose) --manifest-path $(top_srcdir)/Cargo.toml
cargo-build-lib: $(CARGO_CONFIGURED)
$(RUST_ENV_VARS) $(CARGO) build --lib $(RUST_BUILD_OPTS) $(rust_verbose)
$(LIBRUSTZCASH): cargo-build
cargo-build-bins: $(CARGO_CONFIGURED)
$(RUST_ENV_VARS) $(CARGO) build --bins $(RUST_BUILD_OPTS) $(rust_verbose)
$(WALLET_TOOL_BIN): cargo-build-bins
$(AM_V_at)cp $(WALLET_TOOL_BUILD) $@
$(LIBRUSTZCASH): cargo-build-lib
$(LIBSECP256K1): $(wildcard secp256k1/src/*) $(wildcard secp256k1/include/*)
$(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) -C $(@D) $(@F)
@ -99,6 +108,7 @@ lib_LTLIBRARIES = $(LIBZCASH_SCRIPT)
bin_PROGRAMS =
noinst_PROGRAMS =
bin_SCRIPTS =
TESTS =
BENCHMARKS =
@ -108,6 +118,7 @@ endif
if BUILD_BITCOIN_UTILS
bin_PROGRAMS += zcash-cli zcash-tx
bin_SCRIPTS += $(WALLET_TOOL_BIN)
endif
LIBZCASH_H = \
@ -129,7 +140,7 @@ LIBZCASH_H = \
zcash/util.h \
zcash/Zcash.h
.PHONY: FORCE cargo-build check-symbols check-security
.PHONY: FORCE cargo-build-lib cargo-build-bins check-symbols check-security
# bitcoin core #
BITCOIN_CORE_H = \
addrdb.h \

524
src/rust/src/wallet_tool.rs Normal file
View File

@ -0,0 +1,524 @@
use std::cmp::min;
use std::env::{self, consts::EXE_EXTENSION};
use std::ffi::OsStr;
use std::fs::File;
use std::io::{self, BufRead, ErrorKind, Stdin};
use std::iter;
use std::panic;
use std::path::{Path, PathBuf};
use std::process::{self, Command, Output};
use std::str::from_utf8;
use std::time::SystemTime;
use backtrace::Backtrace;
use gumdrop::{Options, ParsingStyle};
use rand::{thread_rng, Rng};
use time::macros::format_description;
use time::OffsetDateTime;
#[derive(Debug, Options)]
struct CliOptions {
#[options(no_short, help = "Print this help output")]
help: bool,
#[options(no_short, help = "Display debugging output")]
debug: bool,
#[options(
no_short,
help = "Specify configuration file (default: zcash.conf)",
meta = "PATH"
)]
conf: Option<String>,
#[options(no_short, help = "Specify data directory")]
datadir: Option<String>,
#[options(no_short, help = "Use the test chain")]
testnet: bool,
#[options(
no_short,
help = "Send commands to node running on IPADDR (default: 127.0.0.1)",
meta = "IPADDR"
)]
rpcconnect: Option<String>,
#[options(
no_short,
help = "Connect to JSON-RPC on PORT (default: 8232 or testnet: 18232)",
meta = "PORT"
)]
rpcport: Option<u16>,
#[options(
no_short,
help = "Username for JSON-RPC connections",
meta = "USERNAME"
)]
rpcuser: Option<String>,
#[options(
no_short,
help = "Password for JSON-RPC connections",
meta = "PASSWORD"
)]
rpcpassword: Option<String>,
#[options(
no_short,
help = "Timeout in seconds during HTTP requests, or 0 for no timeout. (default: 900)",
meta = "SECONDS"
)]
rpcclienttimeout: Option<u32>,
}
impl CliOptions {
fn to_zcash_cli_options(&self) -> Vec<String> {
iter::empty::<String>()
.chain(self.conf.as_ref().map(|o| format!("-conf={}", o)))
.chain(self.datadir.as_ref().map(|o| format!("-datadir={}", o)))
.chain(if self.testnet {
Some("-testnet".into())
} else {
None
})
.chain(
self.rpcconnect
.as_ref()
.map(|o| format!("-rpcconnect={}", o)),
)
.chain(self.rpcport.as_ref().map(|o| format!("-rpcport={}", o)))
.chain(self.rpcuser.as_ref().map(|o| format!("-rpcuser={}", o)))
.chain(
self.rpcpassword
.as_ref()
.map(|o| format!("-rpcpassword={}", o)),
)
.chain(
self.rpcclienttimeout
.as_ref()
.map(|o| format!("-rpcclienttimeout={}", o)),
)
.collect()
}
}
pub fn main() {
// Allow either Bitcoin-style or GNU-style arguments.
let mut args = env::args();
let command = args.next().expect("argv[0] should exist");
let args: Vec<_> = args
.map(|s| {
if s.starts_with('-') && !s.starts_with("--") {
format!("-{}", s)
} else {
s
}
})
.collect();
const USAGE_NOTE: &str = concat!(
"Options can be given in GNU style (`--conf=CONF` or `--conf CONF`),\n",
"or in Bitcoin style with a single hyphen (`-conf=CONF`).\n"
);
let opts = CliOptions::parse_args(&args, ParsingStyle::default()).unwrap_or_else(|e| {
eprintln!(
"{}: {}\n\nUsage: {} [OPTIONS]\n\n{}\n\n{}",
command,
e,
command,
CliOptions::usage(),
USAGE_NOTE,
);
process::exit(2);
});
if opts.help_requested() {
println!(
"Usage: {} [OPTIONS]\n\n{}\n\n{}",
command,
opts.self_usage(),
USAGE_NOTE
);
process::exit(0);
}
if let Err(e) = run(&opts) {
eprintln!("{}: {}", command, e);
process::exit(1);
}
}
fn run(opts: &CliOptions) -> Result<(), io::Error> {
let cli_options: Vec<String> = opts.to_zcash_cli_options();
println!(concat!(
"To reduce the risk of loss of funds, we're going to confirm that the\n",
"zcashd wallet is backed up reliably.\n\n",
" 👛 ➜ 🗃️ \n"
));
println!("Checking that we can connect to zcashd...");
let zcash_cli = zcash_cli_path(opts.debug)?;
// Pass an invalid filename, "\x01", and use the error message to distinguish
// whether zcashd is running with the -exportdir option, running without that
// option, or not running / cannot connect.
let mut cli_args = cli_options.clone();
cli_args.extend_from_slice(&["z_exportwallet".to_string(), "\x01".to_string()]);
let out = exec(&zcash_cli, &cli_args, opts.debug)?;
let cli_err: Vec<_> = from_utf8(&out.stderr)
.map_err(|_| io::Error::from(ErrorKind::InvalidData))?
.lines()
.map(|s| s.trim_end_matches('\r'))
.collect();
if cli_err.len() < 3 || cli_err[0] != "error code: -4" || cli_err[1] != "error message:" {
// TODO: distinguish not running case more precisely
println!(concat!(
"\nNo, we could not connect. zcashd might not be running; in that case\n",
"please start it. The '-exportdir' option should be set to the absolute\n",
"path of the directory you want to save the wallet export file to.\n\n",
"(Don't forget to restart zcashd without '-exportdir' after finishing\n",
"the backup, if running it long-term with that option is not desired\n",
"or would be a security hazard in your environment.)\n\n",
"If you believe zcashd is running, it might be using an unexpected port,\n",
"address, or authentication options for the RPC interface, for example.\n",
"In that case try to connect to it using zcash-cli, and if successful,\n",
"use the same connection options for zcashd-wallet-tool (see '--help' for\n",
"accepted options) as for zcash-cli.\n"
));
return Err(io::Error::from(ErrorKind::Other));
}
if cli_err[2].contains("zcashd -exportdir") {
println!(concat!(
"\nIt looks like zcashd is running without the '-exportdir' option.\n\n",
"Please start or restart zcashd with '-exportdir' set to the absolute\n",
"path of the directory you want to save the wallet export file to.\n",
"(Don't forget to restart zcashd without '-exportdir' after finishing\n",
"the backup, if running it long-term with that option is not desired\n",
"or would be a security hazard in your environment.)"
));
return Err(io::Error::from(ErrorKind::Other));
}
println!("Yes, and it is running with the '-exportdir' option as required.");
let mut stdin = io::stdin();
let base = default_filename_base();
let mut r = 0u32;
let out = loop {
let default_filename = if r != 0 {
format!("{}r{}", base, r)
} else {
base.to_string()
};
println!(
concat!(
"\nEnter the filename for the wallet export file, using only characters\n",
"a-z, A-Z and 0-9 (default '{}')."
),
default_filename
);
let mut buf = String::new();
let response = prompt(&mut stdin, &mut buf)?;
let filename = if response.is_empty() {
r = r.saturating_add(1);
&default_filename
} else {
response
};
if opts.debug {
eprintln!("DEBUG: Using filename {:?}", filename);
}
let mut cli_args = cli_options.clone();
cli_args.extend_from_slice(&["z_exportwallet".to_string(), filename.to_string()]);
let out = exec(&zcash_cli, &cli_args, opts.debug)?;
let cli_err: Vec<_> = from_utf8(&out.stderr)
.map_err(|_| io::Error::from(ErrorKind::InvalidData))?
.lines()
.map(|s| s.trim_end_matches('\r'))
.collect();
if opts.debug {
eprintln!("DEBUG: stderr {:?}", cli_err);
}
if cli_err.len() >= 3
&& cli_err[0] == "error code: -8"
&& cli_err[1] == "error message:"
&& cli_err[2].contains("overwrite existing")
{
println!(concat!(
"That file already exists. Please pick a unique filename in the\n",
"directory specified by the '-exportdir' option to zcashd."
));
continue;
} else {
break out;
}
};
let cli_out: Vec<_> = from_utf8(&out.stdout)
.map_err(|_| io::Error::from(ErrorKind::InvalidData))?
.lines()
.map(|s| s.trim_end_matches('\r'))
.collect();
if opts.debug {
eprintln!("DEBUG: stdout {:?}", cli_out);
}
if cli_out.is_empty() {
return Err(io::Error::from(ErrorKind::InvalidData));
}
let export_path = cli_out[0];
println!("\nSaved the export file to '{}'.", export_path);
println!("IMPORTANT: This file contains secrets that allow spending all wallet funds.\n");
let export_file = File::open(export_path)?;
let phrase_line: Vec<_> = io::BufReader::new(export_file)
.lines()
.filter(|s| {
s.as_ref()
.map(|t| t.starts_with("# - recovery_phrase=\""))
.unwrap_or(false)
})
.collect();
if phrase_line.len() != 1 || phrase_line[0].is_err() {
return Err(io::Error::from(ErrorKind::InvalidData));
}
let phrase = phrase_line[0]
.as_ref()
.unwrap()
.trim_start_matches("# - recovery_phrase=\"")
.trim_end_matches('"');
// This panic hook allows us to make a best effort to clear the screen (and then print
// another reminder about secrets in the export file) even if a panic occurs.
let moved_export_path = export_path.to_string(); // borrow checker workaround
let old_hook = panic::take_hook();
panic::set_hook(Box::new(move |panic_info| {
clear_and_show_cautions(&moved_export_path);
let s = panic_info.payload().downcast_ref::<&str>().unwrap_or(&"");
eprintln!("\nPanic: {}\n{:?}", s, Backtrace::new());
}));
let res = (|| -> io::Result<()> {
println!("The recovery phrase is:\n");
const WORDS_PER_LINE: usize = 3;
let words: Vec<_> = phrase.split(' ').collect();
let max_len = words.iter().map(|w| w.len()).max().unwrap_or(0);
for (i, word) in words.iter().enumerate() {
print!("{0:2}: {1:2$}", i + 1, word, max_len + 2);
if (i + 1) % WORDS_PER_LINE == 0 {
println!();
}
}
if words.len() % WORDS_PER_LINE != 0 {
println!();
}
println!(concat!(
"\nPlease write down this phrase on something durable that you will keep\n",
"in a secure location.\n",
"Press Enter when finished; then the phrase will disappear and you'll be\n",
"asked to re-enter a selection of words from it."
));
let mut stdin = io::stdin();
let mut buf = String::new();
prompt(&mut stdin, &mut buf)?;
// The only reliable and portable way to make sure the recovery phrase
// is no longer displayed is to clear the whole terminal (including
// scrollback, if possible). The text is only printed if clearing fails.
try_to_clear(concat!(
"\n\n\n\n\n\n\n\n\n\n\n\n",
"Please adjust the terminal window so that you can't see the\n",
"recovery phrase above. After finishing the backup, clear the\n",
"terminal window"
));
println!("\nNow we're going to confirm that you backed up the recovery phrase.");
let mut rng = thread_rng();
let mut unconfirmed: Vec<usize> = (0..words.len()).collect();
for _ in 0..min(3, words.len()) {
let index: usize = rng.gen_range(0..unconfirmed.len());
let n = unconfirmed[index];
unconfirmed[index] = unconfirmed[unconfirmed.len() - 1];
unconfirmed.pop().expect("should be nonempty");
loop {
let mut buf = String::new();
println!("\nPlease enter the {} word:", ordinal(n + 1));
let line = prompt(&mut stdin, &mut buf)?;
if words[n] == line {
break;
}
println!("That's not correct, please try again.");
}
}
Ok(())
})();
panic::set_hook(old_hook);
clear_and_show_cautions(export_path);
res?;
let mut cli_args = cli_options;
cli_args.extend_from_slice(&["walletconfirmbackup".to_string(), phrase.to_string()]);
// Always pass false for debug to avoid printing the recovery phrase.
exec(&zcash_cli, &cli_args, false)
.and_then(|out| {
let cli_err: Vec<_> = from_utf8(&out.stderr)
.map_err(|_| io::Error::from(ErrorKind::InvalidData))?
.lines()
.map(|s| s.trim_end_matches('\r'))
.collect();
if opts.debug {
eprintln!("DEBUG: stderr {:?}", cli_err);
}
if !cli_err.is_empty() {
// TODO: distinguish errors: cannot connect, vs incorrect passphrase
// (RPC_WALLET_PASSPHRASE_INCORRECT), vs other errors.
Err(io::Error::from(ErrorKind::Other))
} else {
println!(concat!(
"\nThe backup of the emergency recovery phrase for the zcashd\n",
"wallet has been successfully confirmed 🙂. You can now use the\n",
"zcashd RPC methods that create keys and addresses in that wallet.\n\n",
"If you use other wallets, their recovery information will need\n",
"to be backed up separately.\n"
));
Ok(())
}
})
.map_err(|e| {
println!(concat!(
"\nzcash-wallet-tool was unable to communicate to zcashd that the\n",
"backup was confirmed. This can happen if zcashd stopped, in which\n",
"case you should try again. If zcashd is still running, please seek\n",
"help or try to use 'zcash-cli -stdin walletconfirmbackup' manually.\n"
));
e
})
}
fn prompt<'a>(input: &mut Stdin, buf: &'a mut String) -> io::Result<&'a str> {
let n = input.read_line(buf)?;
if n == 0 {
return Err(io::Error::from(ErrorKind::UnexpectedEof));
}
Ok(buf.trim_end_matches(|c| c == '\r' || c == '\n').trim())
}
fn ordinal(num: usize) -> String {
let suffix = if (11..=13).contains(&(num % 100)) {
"th"
} else {
match num % 10 {
1 => "st",
2 => "nd",
3 => "rd",
_ => "th",
}
};
format!("{}{}", num, suffix)
}
fn zcash_cli_path(debug: bool) -> io::Result<PathBuf> {
// First look for `zcash_cli[.exe]` as a sibling of the executable.
let mut exe = env::current_exe()?;
exe.set_file_name("zcash-cli");
exe.set_extension(EXE_EXTENSION);
if debug {
eprintln!("DEBUG: Testing for zcash-cli at {:?}", exe);
}
if exe.exists() {
return Ok(exe);
}
// If not found there, look in `../src/zcash_cli[.exe]` provided
// that `src` is a sibling of `target`.
exe.pop(); // strip filename
exe.pop(); // ..
if exe.file_name() != Some(OsStr::new("target")) {
// or in `../../src/zcash_cli[.exe]` under the same proviso
exe.pop(); // ../..
if exe.file_name() != Some(OsStr::new("target")) {
return Err(io::Error::from(ErrorKind::NotFound));
}
}
// Replace 'target/' with 'src/'.
exe.set_file_name("src");
exe.push("zcash-cli");
exe.set_extension(EXE_EXTENSION);
if debug {
eprintln!("DEBUG: Testing for zcash-cli at {:?}", exe);
}
if exe.exists() {
Ok(exe)
} else {
Err(io::Error::from(ErrorKind::NotFound))
}
}
fn exec(exe_path: &Path, args: &[String], debug: bool) -> Result<Output, io::Error> {
if debug {
eprintln!("DEBUG: Running {:?} {:?}", exe_path, args);
}
Command::new(exe_path).args(args).output()
}
fn default_filename_base() -> String {
let format = format_description!("export[year][month][day]");
// We use the UTC date because there is a security issue in obtaining the local date
// from either `chrono` or `time`: <https://github.com/chronotope/chrono/issues/602>.
// We could use the approach in
// <https://github.com/ArekPiekarz/rusty-tax-break/commit/3aac8f0c26fd96b7365619509a544f78b59627fe>
// if it were important, but it isn't worth the dependency on `tz-rs`.
OffsetDateTime::from(SystemTime::now())
.format(&format)
.unwrap_or_else(|_| "export".to_string())
}
fn clear_and_show_cautions(export_path: &str) {
try_to_clear(concat!(
"\nCAUTION: This terminal window might be showing secrets (or have\n",
"them in the scrollback). Please copy any useful information and\n",
"then clear it"
));
println!(
concat!(
"\nIMPORTANT: Secrets that allow spending all zcashd wallet funds\n",
"have been left in the file '{}'.\n\n",
"Don't forget to restart zcashd without '-exportdir', if running it\n",
"long-term with that option is not desired or would be a security\n",
"hazard in your environment."
),
export_path,
);
}
fn try_to_clear(error_blurb: &str) {
if let Err(e) = clearscreen::clear() {
eprintln!("Unable to clear screen: {}.", e);
#[cfg(target_os = "windows")]
const CLEAR: &str = "cls";
#[cfg(not(target_os = "windows"))]
const CLEAR: &str = "clear";
println!("{} using '{}'.", error_blurb, CLEAR);
}
}

View File

@ -1750,7 +1750,11 @@ UniValue walletconfirmbackup(const UniValue& params, bool fHelp)
if (fHelp || params.size() != 1)
throw runtime_error(
"walletconfirmbackup \"emergency recovery phrase\"\n"
"\nNotify the wallet that the user has backed up the emergency recovery phrase,\n"
"\nCAUTION: This is an internal method that is not intended to be called directly by\n"
"users. Please use the zcashd-wallet-tool utility (built or installed in the same directory\n"
"as zcashd) instead. In particular, this method should not be used from zcash-cli, in order\n"
"to avoid exposing the recovery phrase on the command line.\n\n"
"Notify the wallet that the user has backed up the emergency recovery phrase,\n"
"which can be obtained by making a call to z_exportwallet. The zcashd embedded wallet\n"
"requires confirmation that the emergency recovery phrase has been backed up before it\n"
"will permit new spending keys or addresses to be generated.\n"

View File

@ -80,6 +80,7 @@ clean_dirs __pycache__
clean_exe src/bench/bench_bitcoin
clean_exe src/zcash-cli
clean_exe src/zcashd
clean_exe src/zcashd-wallet-tool
clean_exe src/zcash-gtest
clean_exe src/zcash-tx
clean_exe src/test/test_bitcoin