9.2 KiB
A Zcash-Devtool Primer
This page documents a full walkthrough of how to set up and use the
zcash-devtool
tooling. It is intended to serve as a guide for how to get set
up and how to add your own functionality to the tool.
Development Environment
In order to work with zcash-devtool
, the first thing you will need is a Rust
development environment. If you don't already have Rust installed, follow the
directions at https://rustup.rs/ to get a Rust toolchain installed.
You'll also need the source code, as there is no binary distribution of
zcash-devtool
. It is built by developers, for developers, for testing and
development of new Zcash functionality; DO NOT commit significant funds
to the management of the zcash-devtool
embedded wallet.
Obtain the source code by cloning the github repository:
λ git clone https://github.com/zcash/zcash-devtool
First Steps
Now we're ready to take our first look at the capabilities that zcash-devtool
provides. We will build and run the tool using cargo run
.
λ cargo run --release --all-features -- --help
This results in output like the following:
Usage: zcash-devtool [COMMAND]
Commands:
inspect Inspect Zcash-related data
wallet Manipulate a local wallet backed by `zcash_client_sqlite`
pczt Send funds using PCZTs
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help
You can get additional information about a given command:
λ cargo run --release --all-features -- wallet --help
Wallet Initialization
For the purposes of this demo, we're going to set up a testnet wallet. We're
going to create the wallet in a ../dev-wallet
directory; this is
intentionally outside of the root of the git repository, just so that we don't
accidentally end up committing wallet data to the repository when we go to
commit code changes. In addition to the wallet databases and configuration, the
initialization process will generate an age
key file at
../dev-wallet/dev-key.txt
; this key will be used to encrypt the wallet's
mnemonic seed. We'll also use the testnet.zec.rocks
server for this setup.
λ cargo run --release --all-features -- wallet -w ../dev-wallet init --name "ZDevTest" \
-i ../dev-wallet/dev-key.txt -n test -s zecrocks
If we look at the ./../dev-wallet
directory now, we can see that a number of
files and directories have been created:
λ ll ../dev-wallet/
total 352
-rw-r--r-- 1 ... ... 16384 Mar 3 17:48 blockmeta.sqlite
drwxrwxr-x 2 ... ... 4096 Mar 3 17:48 blocks/
-rw-r--r-- 1 ... ... 323584 Mar 3 17:48 data.sqlite
-rw------- 1 ... ... 189 Mar 3 16:59 dev-key.txt
-rw-rw-r-- 1 ... ... 786 Mar 3 17:48 keys.toml
drwxrwxr-x 4 ... ... 4096 Mar 3 17:43 tor/
A look in the keys.toml
file will show us our (encrypted) key, along with
basic wallet metadata:
λ cat ../dev-wallet/keys.toml
mnemonic = """
-----BEGIN AGE ENCRYPTED FILE-----
<...>
-----END AGE ENCRYPTED FILE-----
"""
network = "test"
birthday = 3274265
Receiving Payments
In order to receive a payment, we'll need an address:
λ cargo run --release --all-features -- wallet -w ../dev-wallet list-addresses
We can use another testnet wallet to send some payments to the wallet, or mine
coins to fund it. If you're running a zcashd testnet node, it's easy to enable
mining by adding the following configuration to your ~/.zcash/zcash.conf
file.
gen=1
equihashsolver=tromp
mineraddress=YOUR_ADDRESS_HERE
Now, in order to detect those funds, we need to scan the chain:
λ cargo run --release --all-features -- wallet -w ../dev-wallet sync -s zecrocks
Having scanned the chain, we can now check the balance
λ cargo run --release --all-features -- wallet -w ../dev-wallet balance
You can explore more of the wallet commands available using:
λ cargo run --release --all-features -- wallet --help
PCZT Support
It's possible to use zcash-devtool
as a "simulated hardware wallet" via use
of the pczt
suite of commands. These commands provide reference
implementations for PCZT creation, proving, signing, combining, inspection, and
more.
This section will simulate an interaction between an online wallet that scans
the chain with a viewing key and an offline wallet that holds the signing keys.
For the purpose of this simulation, the ../dev-wallet
wallet we've created
will take on the role of our hardware device.
First, we're going to set up our "online wallet" by exporting a viewing key from the development wallet, and using it to initialize a new view-only wallet.
λ cargo run --release --all-features -- wallet -w ../dev-wallet/ list-accounts
That produces output like the following:
Account ef4cde11-ac2b-4ac3-becf-3df518ee2c97
Name: ZDevTest
UIVK: <...>
UFVK: <dev_ufvk>
Source: Derived { derivation: Zip32Derivation { seed_fingerprint: SeedFingerprint(<dev_seed_fp>), account_index: AccountId(0) }, key_source: None }
Using the UFVK value provided there, we can initialize a new view-only wallet that we'll use as the online device in this exchange. After initializing it, we'll also sync that wallet. It is important to also include the seed fingerprint and HD account index, because we will need those to make it easy for the signing wallet to choose the correct key.
λ cargo run --release --all-features -- wallet -w ../view-wallet/ init-fvk \
--name ZDevView \
--fvk "<dev_ufvk>" \
--birthday 3274265 \
--seed-fingerprint "<dev_seed_fp>" \
--hd-account-index 0 \
-s zecrocks
λ cargo run --release --all-features -- wallet -w ../view-wallet sync -s zecrocks
Asking for the balance here should show the same balance as we saw in our original wallet:
λ cargo run --release --all-features -- wallet -w ../view-wallet balance
We're now going to use the view-only wallet to create a PCZT. If you don't have a separate testnet wallet to receive these funds, you can always use the wallet's own address, or you can create a separate wallet to better simulate multiple independent wallets interacting.
λ cargo run --release --all-features -- pczt -w ../view-wallet create --address <...> --value 12340000 --memo "Hello from a PCZT!" > ../test_pczt.created
We can inspect that with the pzct inspect
subcommand.
λ cargo run --release --all-features -- pczt inspect < ../test_pczt.created
We can create the proofs for that PCZT using the view-only (online) wallet:
λ cargo run --release --all-features -- pczt -w ../view-wallet prove < ../test_pczt.created > ../test_pczt.proven
Now, using the "offline" wallet (our original ../dev-wallet
), we're going to
add the signatures. Note that we use ../test_pczt.created
as the input, not
the version containing the proofs. Once we've created both the signed and
proven PCZTs separately, we'll combine them in a later step. Note that, because
we're making signatures, we have to provide the age
identity file to use to
decrypt the spending key.
λ cargo run --release --all-features -- pczt -w ../dev-wallet sign --identity ../dev-wallet/dev-key.txt < ../test_pczt.created > ../test_pczt.signed
With proofs and signatures complete, we can combine the PCZTs. This operation doesn't require a wallet at all.
λ cargo run --release --all-features -- pczt combine -i ../test_pczt.signed -i ../test_pczt.proven > ../test_pczt.combined
Now all that's left is to extract the finished transaction, and send it to the chain. We'll do this using the "online" view-only wallet.
λ cargo run --release --all-features -- pczt -w ../view-wallet/ send -s zecrocks < ../test_pczt.combined
Local Development
It's often useful to build with local versions of the crates that
zcash-devtool
depends upon. Doing so is easy; just add a set of patch
directives to the root Cargo.toml
file. The example below assumes that you
have librustzcash and
incrementalmerkletree checked
out locally. With these patch directives in place, local changes to the relevant
crates will be immediately usable in the devtool.
[patch.crates-io]
equihash = { path = "../librustzcash/components/equihash/" }
pczt = { path = "../librustzcash/pczt/" }
transparent = { package = "zcash_transparent", path = "../librustzcash/zcash_transparent/" }
zcash_address = { path = "../librustzcash/components/zcash_address/" }
zcash_client_backend = { path = "../librustzcash/zcash_client_backend" }
zcash_client_sqlite = { path = "../librustzcash/zcash_client_sqlite" }
zcash_encoding = { path = "../librustzcash/components/zcash_encoding/" }
zcash_keys = { path = "../librustzcash/zcash_keys/" }
zcash_primitives = { path = "../librustzcash/zcash_primitives" }
zcash_proofs = { path = "../librustzcash/zcash_proofs" }
zcash_protocol = { path = "../librustzcash/components/zcash_protocol" }
zip321 = { path = "../librustzcash/components/zip321/" }
incrementalmerkletree = { path = "../incrementalmerkletree/incrementalmerkletree/" }
shardtree = { path = "../incrementalmerkletree/shardtree/" }