Just five days left to register for voting in Project Catalyst: https://docs.projectcatalyst.io/catalyst-basics/fund10/fund10-timeline
So, it’s high time to stop procrastinating and do that registration!
In this post, I’m going to follow @ATADA’s excellent guide at https://github.com/gitmachtl/scripts/blob/master/Catalyst_Registration_CLI_Tools.md#the-step-by-step-way-on-the-cli-new to generate my Catalyst voting registration on command-line and register all my accounts to the same voting key.
Goal is that I only have to vote once. Last fund, I registered my accounts separately (only possibility in the wallet apps) and had to log out and replay the complete slate of votes with the second account. Since I by now have three active accounts with considerable ADA/voting power on them, I really don’t want to do that again.
Installing the software
What we need:
cardano-cli
andbech32
from https://github.com/input-output-hk/cardano-node/releases (latest stable version v8.1.2)cardano-hw-cli
from https://github.com/vacuumlabs/cardano-hw-cli/releases (if we want to register hardware wallets, v1.12.0 although v1.13.0 is latest)cardano-address
from https://github.com/input-output-hk/cardano-addresses/releases (if we want to register seed phrase/software wallets, latest version 3.12.0)catdano-signer
from https://github.com/gitmachtl/cardano-signer/releases (latest version 1.13.0)catalyst-toolbox
from https://github.com/input-output-hk/catalyst-toolbox/releases (latest version 0.5.0)
Some steps also need jq
installed which should be available from your Linux distribution in the usual way.
I keep the binary releases of Cardano software I install in ~/Cardano/Software/
:
$ ls -l ~/Cardano/Software/
[…]
drwxr-xr-x 1 sean sean 50 2023-07-23 14:07 cardano-addresses-3.12.0/
-rw-r--r-- 1 sean sean 4819241 2023-07-23 14:06 cardano-addresses-3.12.0-linux64.tar.gz
drwxr-xr-x 1 sean sean 74 2022-09-20 15:07 cardano-hw-cli-1.12.0/
-rw-r--r-- 1 sean sean 28280732 2023-07-16 00:33 cardano-hw-cli-1.12.0_linux-x64.tar.gz
[…]
drwxr-xr-x 1 sean sean 540 2023-07-24 18:17 cardano-node-8.1.2/
-rw-r--r-- 1 sean sean 147644811 2023-08-12 22:57 cardano-node-8.1.2-linux.tar.gz
drwxr-xr-x 1 sean sean 28 2023-07-16 00:38 cardano-signer-1.13.0/
-rw-r--r-- 1 sean sean 15218575 2023-07-16 00:33 cardano-signer-1.13.0_linux-x64.tar.gz
drwxr-xr-x 1 sean sean 32 2023-07-16 00:39 catalyst-toolbox-0.5.0/
-rw-r--r-- 1 sean sean 8967183 2023-07-16 00:32 catalyst-toolbox-0.5.0-x86_64-unknown-linux-gnu.tar.gz
[…]
And then I link to them from ~/.local/bin/
to have a version of my choice in the PATH
:
$ ls -l ~/.local/bin/
[…]
lrwxrwxrwx 1 sean sean 53 2023-08-12 23:05 bech32 -> /home/sean/Cardano/Software/cardano-node-8.1.2/bech32*
lrwxrwxrwx 1 sean sean 72 2023-07-23 14:07 cardano-address -> /home/sean/Cardano/Software/cardano-addresses-3.12.0/bin/cardano-address*
lrwxrwxrwx 1 sean sean 58 2023-08-12 23:06 cardano-cli -> /home/sean/Cardano/Software/cardano-node-8.1.2/cardano-cli*
lrwxrwxrwx 1 sean sean 64 2023-08-12 23:09 cardano-hw-cli -> /home/sean/Cardano/Software/cardano-hw-cli-1.12.0/cardano-hw-cli*
[…]
lrwxrwxrwx 1 sean sean 64 2023-07-16 00:41 cardano-signer -> /home/sean/Cardano/Software/cardano-signer-1.13.0/cardano-signer*
lrwxrwxrwx 1 sean sean 67 2023-07-16 00:41 catalyst-toolbox -> /home/sean/Cardano/Software/catalyst-toolbox-0.5.0/catalyst-toolbox*
[…]
You can obviously do as you please and how you want to manage your system. If you like to call the binaries with their full path, they also do not have to be in the search PATH
.
Generating a voting key pair and a QR code for it
If you already participated in Catalyst or read a bit about it, you know that you log in to the Catalyst Voting app with a QR code and a PIN.
The QR code is the private key of your voting key pair. Keep it safe! If someone gets it they can vote as you.
(It is just protected by that four-digit PIN. Four-digit PINs are enough for your debit or credit card, because they lock you out with the need for either a new PIN or a reset by the help desk after three attempts. But here, an attacker can try as many as they like offline. In that case, four digits are a joke.)
If you register with one of the wallet apps, those are generated for you by the wallet app. As far as I know, all wallet apps generate a random key pair. For software wallets, they could also use a key derivation from your seed phrase/root key. For hardware wallets, that’s totally impossible, since private keys do not leave the hardware devices.
And because the key pair is random and does not have much to do with the rest of your wallet (until the registration done in the next two sections happens), wallet apps can only show you the QR code again if they save it and you are in exactly the same instance of the wallet app that (still) has that saved state. Otherwise, you need to do a new registration if you lose QR code or PIN.
So, let’s do what the wallet apps do on the command line with cardano-signer
and catalyst-toolbox
:
$ cardano-signer keygen --cip36 --json-extended --out-skey voting.skey --out-vkey voting.vkey --out-file voting.json
$ catalyst-toolbox qr-code encode --pin 1234 --input <(cat voting.skey | jq -r .cborHex | cut -c 5-132 | bech32 "ed25519e_sk") --output voting.png img
We end up with a private key in voting.skey
, a public key in voting.vkey
, both and some additional information (including a seed phrase that will in later funds be used in wallet apps to vote) in voting.json
and the QR code in voting.png
.
We now need to register/delegate some accounts to this voting key.
Registering hardware wallet accounts to the voting key
To prepare, I’m getting the payment and stake public keys and hardware signing files from the Ledger:
$ cardano-hw-cli address key-gen --path 1852H/1815H/0H/0/0 --verification-key-file ledger1payment.vkey --hw-signing-file ledger1payment.hwsfile
$ cardano-hw-cli address key-gen --path 1852H/1815H/0H/2/0 --verification-key-file ledger1stake.vkey --hw-signing-file ledger1stake.hwsfile
The Ledger will ask for confirmation to export public keys.
I then construct the address (which is the single address of this account) from it:
$ cardano-cli address build --mainnet --payment-verification-key-file ledger1payment.vkey --stake-verification-key-file ledger1stake.vkey > ledger1address
(I could have also just copy-pasted it from my wallet app. It should be the same.)
Now, I can generate the voting registration metadata that will be submitted to the chain in the next step:
$ cardano-hw-cli catalyst voting-key-registration-metadata --mainnet --reward-address $(cat ledger1address) --reward-address-signing-key ledger1stake.hwsfile --reward-address-signing-key ledger1payment.hwsfile --vote-public-key <(cat voting.vkey | jq -r .cborHex | cut -c 5- | bech32 "ed25519e_pk") --stake-signing-key ledger1stake.hwsfile --nonce 100366310 --metadata-cbor-out-file ledger1registration.cbor
It is recommended to use the current slot as nonce (which at the time of writing – cardano-cli query tip --mainnet
– is 100366310).
The Ledger will ask for confirmation for signing a “transaction” (which in this case is not really a transaction, has no outputs and no fee, but just signs the registration with the payment as well as the stake key).
Now, the only thing remaining is to submit this registration. I can either look up a UTxO to use as input in my wallet app or use cardano-cli query utxo --mainnet --address $(cat ledger1address)
. Then, I build the transaction with:
$ cardano-cli transaction build --mainnet --tx-in 32010661815ac540b518b6c7c7545cf89e770931cff0527414a7644fb766e992#1 --change-address $(cat ledger1address) --metadata-cbor-file ledger1registration.cbor --out-file ledger1registration.txunsigned
Estimated transaction fee: Lovelace 176281
I sign it with:
$ cardano-hw-cli transaction witness --mainnet --tx-file ledger1registration.txunsigned --hw-signing-file ledger1payment.hwsfile --out-file ledger1registration.txwitness
$ cardano-cli transaction assemble --tx-body-file ledger1registration.txunsigned --witness-file ledger1registration.txwitness --out-file ledger1registration.txsigned
And I finally submit it with:
$ cardano-cli transaction submit --mainnet --tx-file ledger1registration.txsigned
Transaction successfully submitted.
Et voilà, the transaction appears in Eternl’s transaction history with all the registration details:
I’m just doing the same steps again with different account numbers in the derivation paths for the second and third accout:
$ cardano-hw-cli address key-gen --path 1852H/1815H/1H/0/0 --verification-key-file ledger2payment.vkey --hw-signing-file ledger2payment.hwsfile
$ cardano-hw-cli address key-gen --path 1852H/1815H/1H/2/0 --verification-key-file ledger2stake.vkey --hw-signing-file ledger2stake.hwsfile
$ cardano-cli address build --mainnet --payment-verification-key-file ledger2payment.vkey --stake-verification-key-file ledger2stake.vkey > ledger2address
$ cardano-hw-cli catalyst voting-key-registration-metadata --mainnet --reward-address $(cat ledger2address) --reward-address-signing-key ledger2stake.hwsfile --reward-address-signing-key ledger2payment.hwsfile --vote-public-key <(cat voting.vkey | jq -r .cborHex | cut -c 5- | bech32 "ed25519e_pk") --stake-signing-key ledger2stake.hwsfile --nonce 100366310 --metadata-cbor-out-file ledger2registration.cbor
$ cardano-cli transaction build --mainnet --tx-in 8f54a0daa930bfd1f69730baf73211f5d69481cb2dd184a86960772b09a61633#2 --change-address $(cat ledger2address) --metadata-cbor-file ledger2registration.cbor --out-file ledger2registration.txunsigned
Estimated transaction fee: Lovelace 176281
$ cardano-hw-cli transaction witness --mainnet --tx-file ledger2registration.txunsigned --hw-signing-file ledger2payment.hwsfile --out-file ledger2registration.txwitness
$ cardano-cli transaction assemble --tx-body-file ledger2registration.txunsigned --witness-file ledger2registration.txwitness --out-file ledger2registration.txsigned
$ cardano-cli transaction submit --mainnet --tx-file ledger2registration.txsigned
Transaction successfully submitted.
$ cardano-hw-cli address key-gen --path 1852H/1815H/2H/0/0 --verification-key-file ledger3payment.vkey --hw-signing-file ledger3payment.hwsfile
$ cardano-hw-cli address key-gen --path 1852H/1815H/2H/2/0 --verification-key-file ledger3stake.vkey --hw-signing-file ledger3stake.hwsfile
$ cardano-cli address build --mainnet --payment-verification-key-file ledger3payment.vkey --stake-verification-key-file ledger3stake.vkey > ledger3address
$ cardano-hw-cli catalyst voting-key-registration-metadata --mainnet --reward-address $(cat ledger3address) --reward-address-signing-key ledger3stake.hwsfile --reward-address-signing-key ledger3payment.hwsfile --vote-public-key <(cat voting.vkey | jq -r .cborHex | cut -c 5- | bech32 "ed25519e_pk") --stake-signing-key ledger3stake.hwsfile --nonce 100366310 --metadata-cbor-out-file ledger3registration.cbor
$ cardano-cli transaction build --mainnet --tx-in 44023a5400e8068f2164222a2f6f34a3f1eeb7d8649e7352a98f7c0696951221#0 --change-address $(cat ledger3address) --metadata-cbor-file ledger3registration.cbor --out-file ledger3registration.txunsigned
Estimated transaction fee: Lovelace 176281
$ cardano-hw-cli transaction witness --mainnet --tx-file ledger3registration.txunsigned --hw-signing-file ledger3payment.hwsfile --out-file ledger3registration.txwitness
$ cardano-cli transaction assemble --tx-body-file ledger3registration.txunsigned --witness-file ledger3registration.txwitness --out-file ledger3registration.txsigned
$ cardano-cli transaction submit --mainnet --tx-file ledger3registration.txsigned
Transaction successfully submitted.
(If you are using accounts not in single address mode and want to have the rewards on another, possibly unused address and pay for the registration transaction from a third, different address, the above process gets a little more complicated: You’d have to get public keys and hardware signing files for their derivation paths, combine them with the stake key to get the addresses, and use them instead at the appropriate places.)
Registering software wallet accounts to the voting key
If you use a seed phrase/software wallet instead of a hardware wallet, the process is a little different. Instead of asking the Ledger with cardano-hw-cli
for public keys and signatures, we derive the keys from the seed phrase with cardano-address
:
$ echo <seed phrase> | cardano-address key from-recovery-phrase Shelley | cardano-address key child 1852H/1815H/0H/0/0 > seedpayment.xsk
$ cardano-cli key convert-cardano-address-key --shelley-payment-key --signing-key-file seedpayment.xsk --out-file seedpayment.skey
$ cardano-cli key verification-key --signing-key-file seedpayment.skey --verification-key-file seedpayment.vkey
$ echo <seed phrase> | cardano-address key from-recovery-phrase Shelley | cardano-address key child 1852H/1815H/0H/2/0 > seedstake.xsk
$ cardano-cli key convert-cardano-address-key --shelley-stake-key --signing-key-file seedstake.xsk --out-file seedstake.skey
$ cardano-cli key verification-key --signing-key-file seedstake.skey --verification-key-file seedstake.vkey
$ cardano-cli address build --mainnet --payment-verification-key-file seedpayment.vkey --stake-verification-key-file seedstake.vkey > seedaddress
You then use cardano-signer
instead of cardano-hw-cli
to get the registration certificate CBOR:
$ cardano-signer sign --cip36 --payment-address $(cat seedaddress) --vote-public-key voting.vkey --secret-key seedstake.skey --out-cbor seedregistration.cbor
And you witness the transaction with cardano-cli
instead of cardano-hw-cli
:
$ cardano-cli transaction build --mainnet --tx-in <some UTxO> --change-address $(cat seedaddress) --metadata-cbor-file seedegistration.cbor --out-file seedregistration.txunsigned
Estimated transaction fee: Lovelace 176281
$ cardano-cli transaction witness --mainnet --tx-body-file seedregistration.txunsigned --signing-key-file seedpayment.skey --out-file seedregistration.txwitness
$ cardano-cli transaction assemble --tx-body-file seedregistration.txunsigned --witness-file seedregistration.txwitness --out-file seedregistration.txsigned
$ cardano-cli transaction submit --mainnet --tx-file seedregistration.txsigned
Transaction successfully submitted.
(Again, if your are not using single address mode derive the keys for the address you want to receive the voting rewards on as well as the address holding the funds to pay for the registration transaction instead.)
Checking it all worked out
The Catalyst team has now given us a new verification tool at https://verify.testnet.projectcatalyst.io/ to check if everything worked.
And – after waiting a while after the registration – it shows the total voting power of all three accounts and all three rewards addresses.
Edit: Given the warning about the QR code being your private key above, it might be counter-intuitive to just give it to this verification web app – even if that web app is from the Catalyst team themselves. Just came across this tweet from Adastat:
So, you can also only import your QR code into the Catalyst Voting app (where it should go anyway) and copy the wallet ID to see your registrations on adastat.net without exposing secrets to an additional app or service:
Perfect! Just voting once with the power of all my accounts together and getting rewards on all of them!