The next Zcash major network upgrade, Sapling, is scheduled to go live on October 28th.
We are not going to explain the benefits of the new version since there is plenty of coverage on the internet already (you can read all about it in our blog post: BitGo Ready for Zcash Sapling). Instead, this article is going to show you how to use it!
To do so, we are going to use bitgo-utxo-lib, an open source library used to build transactions for UTXO (unspent transaction output) coins, including Zcash, Bitcoin, Bitcoin Gold, Bitcoin Cash, Dash, and Litecoin.
Moreover, since at BitGo we support multisig wallets based on their security features, I’m going to show you how to spend ZEC (Zcash currency) from a multisig address after Zcash Sapling network upgrade.
If you are familiar with Zcash you should notice that this tutorial builds a transparent transaction, not a shielded one since these are more complex and not yet widely supported. One of Sapling’s goals is to change this by improving the performance of proof building and validation.
Zcash was forked from Bitcoin Core code (not a chain fork!) and the two share many concepts. This tutorial is targeted at developers familiar with either coin.
If you want to build a transaction yourself rather than following the example, you will need:
First, let’s create a new folder, initialize a default npm project, and add bitgo-utxo-lib to the project dependencies. To do so, open a terminal window and run:
Now that we have the project set up, open index.js file and import bitgo-utxo-lib by adding the following line at the top:
The next step is to instantiate the transaction builder object and configure it to craft transactions following the Zcash Sapling protocol.
bitgo-utxo-lib uses network objects to specify which protocol to use. These encapsulate blockchain specific parameters like bip32 version byte, wif prefix, consensusBranchId (Zcash specific), and more… You can see all network specific parameters in the network file in GitHub.
Add the following lines to the script:
Now that we have a builder, let’s set up the basic fields required by a Sapling transaction: version and group id.
Sapling version number is 4 and it succeeds Overwinter (version 3). Previous to that we had version 1 for the Sprout release.
Group id is a field introduced in the Overwinter upgrade as a network upgrade mechanism for transaction parsing.
For Sapling, the id changed from 0x03C48270 (Overwinter) to 0x892F2085.
You set these fields in the builder as follows:
There are two other optional, but popular, fields worth mentioning:
Lock time: Defines the block height after which the transaction can be included in a block by a miner. Zero means it can be immediately added to the blockchain.
Expiry height: Defines the block height after which the transaction will be removed from the mempool if they have not been mined. Zero means there is no expiration time.
In this example, we disabled the lock time, meaning the transaction can be picked by a miner as soon as it shows up in the mempool but it can only be included in a block before the blockchain reaches block 289507.
In the UTXO model, a transaction is made from inputs and outputs.
From the new transaction perspective, inputs are the outputs of a funding transaction which have to be unspent (not referenced by any other transaction in the blockchain).
We have multiple ways to prepare and add inputs using the builder; in this example, we will directly reference the transaction id and output index with the unspent ZEC we will use:
Now, the outputs of a transaction are the different destinations we want the funds in the input to go. In this case, we will transfer most of the funds to a single address. To do so you only need the destination address and value.
The total amount available in our input transaction is 2 ZEC (200000000 zatoshis), but as you can see, we are only transferring 199999000 zatoshis. This is because we need to pay a fee to the miners and the remaining 1000 zatoshis are used for that.
Now that we have our builder with the Zcash Sapling parameters, the inputs to use and the outputs to send the funds to, we have to sign the transaction in order to unlock the funds.
Since the funds used by this transaction are in a 2-of-3 multisig address, we require 2 signatures to unlock the funds.
When we create a multisig address with the 3 public keys of the joint owners' account, we are given a redeemscript. In our example, this looks like:
We can decode the raw hex using bitgo-utxo-lib like this:
The script is executed every time we try to move funds out of this address and what it’s saying is that to unlock the funds, it needs at least 2 (OP2) out of 3 (OP3 ) ECDSA signatures to match (OP_CHECKMULTISIG) one of the public keys (021… 03c… 02f… ). The combination of the signatures with the public keys proves the transaction was created by the real owner of the address in question.
We will need one last parameter, hashType, in order to tell the builder how to sign the transaction. In this example, we are using the default value, SIGHASHALL, which means that every output and input in the transaction will be signed. Other hash types like SIGHASHNONE sign the input but leave the outputs unsigned so miners can later change the destination address. We obviously don’t want the latter.
The Zcash protocol requires us to sign the transaction with the value of the inputs. In other words:
This extra bit of information helps offline signing devices to calculate the exact amount being spent and transaction fees without having to pull the inputs transaction from the network. This helps with the implementation of lightweight, air-gapped wallets. In our example, the amount is in the constant inputValueToSign.
Add the following lines to the script in order to sign the transaction output at index 0:
If we had more outputs, like a change address, we would have to sign those as well by repeating builder.sign step with a different index.
Once the transaction builder has the version, group id, inputs, outputs, and signatures, we are ready to build our Zcash Sapling transaction hex by adding the following lines:
The build function generates the script signature for each input depending on the script type provided.
In the command line window run:
You should see the following transaction hex as output:
If you got the same transaction hex, then congrats! You built your first Zcash Sapling compatible transaction.
Finally, to broadcast it into the Zcash network, we have to find a node to do it for us. I used https://explorer.testnet.z.cash/tx/send since it is updated with the latest Sapling compatible software.
Once we hit the “Send transaction” button, our transaction will go to the mempool and wait for a miner to pick it up. This particular transaction can be seen here: https://explorer.testnet.z.cash/tx/20ec1ae4eb2082499c0014a520c13266b18dd134d65274de4f3adca701c9042f
Note that if you try to broadcast this very same transaction you will get the message:
This is because the expiry height value has been passed already. If you try to change it for a valid one and broadcast it, you will get:
And that’s because the funds in the input used in this example have been used already when doing this tutorial.
We’ve seen how to spend the funds from a Zcash multisig address by manually building a Sapling compatible transaction with bitgo-utxo-lib and broadcasting it using a public node.
Most part of this tutorial works for other cryptocurrencies like Bitcoin, you just have to change the network and make sure you are using the right parameters. For instance, version group id and expiry height only apply for Zcash, other coins don’t use these.
You can find the full example in GitHub gist: https://gist.github.com/argjv/289c80dbe89c43179f6f543ed94283ea#file-zecsaplingmultisig-js