Registering several wallets/accounts to the same Catalyst voting key

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:

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!

9 Likes

Hey man,

super cool guide also for seedphrase based wallets.
Just as a little extra info, cardano-signer can generate keys from mnemonics(seedphrase).
So there is no need to also do the fiddling with cardano-address :slight_smile:

For payment keys you simple call:
./cardano-signer keygen --mnemonics "<seed phrase>" --path payment --out-vkey seedpayment.vkey
or
./cardano-signer keygen --mnemonics "<seed phrase>" --path 1852H/1815H/0H/0/0 payment --out-vkey seedpayment.vkey

But as you only need the stake skey for the cbor registration you can do it via:
./cardano-signer keygen --mnemonics "<seed phrase>" --path stake --out-skey seedstake.skey
or
./cardano-signer keygen --mnemonics "<seed phrase>" --path 1852H/1815H/0H/2/0 --out-skey seedstake.skey

Full options:

Generate Cardano ed25519/ed25519-extended keys:

   Syntax: cardano-signer keygen
   Params: [--path "<derivationpath>"]                          optional derivation path in the format like "1852H/1815H/0H/0/0" or "1852'/1815'/0'/0/0"
                                                                or predefined names: --path payment, --path stake, --path cip36, --path drep
           [--mnemonics "word1 word2 ... word24"]               optional mnemonic words to derive the key from (separate via space)
           [--cip36]                                            optional flag to generate CIP36 conform vote keys (also using path 1694H/1815H/0H/0/0)
           [--vote-purpose <unsigned_int>]                      optional vote-purpose (unsigned int) together with --cip36 flag, default: 0 (Catalyst)
           [--with-chain-code]                                  optional flag to generate a 128byte secretKey and 64byte publicKey with chain code
           [--json | --json-extended]                           optional flag to generate output in json/json-extended format
           [--out-file "<path_to_file>"]                        path to an output file, default: standard-output
           [--out-skey "<path_to_skey_file>"]                   path to an output skey-file
           [--out-vkey "<path_to_vkey_file>"]                   path to an output vkey-file
   Output: "secretKey + publicKey" or JSON-Format               default: hex-format

Best regards,
Martin

4 Likes

Thank you so much for this guide, I was expecting to have to repeat my votes with all my accounts once again when I stumble upon your post by accident!
I followed the procedure. Do you know how long I should wait for Adastat to update with the account id details before concluding it failed?

I also had to use version 1.12 of cardano-hw-cli because the latest version requires Cardano app version 6.0.2 with cip36 support but only 5.0.1 is available in the Ledger Live software…

Don’t know.

The Catalyst verification page said somethimg like “check after 24 hours”, but it showed my registrations after two hours. I suppose Adastat should be much faster, since it is built to show current state of the chain as soon as possible.

Do you see your registration transactions including vote registration metadata on-chain?

That’s what the original guide also said and what I meant with:

Could maybe have been clearer.

It finally showed up on Adastat, it takes about the same time as on the Catalyst tool it seems, maybe slightly less but I’m not certain.

1 Like

Good to know about these methods but I found myself just using the old doc for voting from a single address. :sweat_smile: @ATADA can you say why you gave this warning here?

https://github.com/gitmachtl/scripts/blob/master/Catalyst_Registration_CLI_Tools.md#3-transmit-the-votingregistration-metadata-cbor-file-on-the-chain-1
And find some funds to use: We need a payment address, this should NOT BE YOUR PLEDGE ADDRESS!

I am feeling paranoid and running up against the registration deadline with not too much time to take this stuff apart. The paranoia I guess is coming from not being sure whether the CBOR built with cardano-signer is not leaving the voter’s stake private key unencrypted in the metadata.

I realise this could be 2 separate questions:

  • Why not send the metadata-carrying transaction from the pledge address, if we can do it securely?
  • Does any part of this process expose the voter’s stake private key?

Hey, no there is no security issue with the generated cbor metadata. The Metadata is only signed with the stake key, like any other signature. But there is no secret stake key included anywhere.

I gave the warning, because the pledge account usually holds a lot of ADA. And there is really no need to do a simple transaction to include the cbor metadata with a wallet that may contain 500.000 ADA or so. Just use a small CLI wallet with a few ADA on it for that. Thats the warning about.

Best regards,
Martin

2 Likes

thanks Martin for both confirmations :sunglasses:

Now that we have gone through the trouble of generating our voting keys by hand, who can post or refer a good tutorial explaining how we will be able to vote without using the Catalyst mobile app?

There has been some preparatory work for this but I am just trying to find a practical assurance that an actual voting process is possible and explainable for Fund 10.

I have heard out-of-band (and can’t elaborate because I don’t know any more details) that the “difficult part” is determining the internal names that are used for the Catalyst proposals. Then (the “easy” part?) one only has to pass a voting request into a Jormungandr REST session somehow. :face_with_spiral_eyes:

hey, there will be the catalyst voting center (web) in the future. starting with fund11. currently the only other way would be to directly communicate with the catalyst api, but there is no piece of ready software to do this in a nice way. so, please be patient and enjoy the catalyst voting center once finished. :slight_smile:

2 Likes

Would have been nice if they had documented that months or years ago.

But, well, now there’s probably no point anymore.

Just hope the voting centre will have an API for alternative user interfaces and scripting and we are not bound to their idea of a UI again.

This is all generated new currently, its all in the making also with 1694 in mind and dReps, etc… not an easy task. You can checkout the API here: Web API - Catalyst Voting System - Core Technology

The Catalyst Voting Center will make a direct connection to a dApp enabled Wallet, i think it will be nice.
https://app.testnet.projectcatalyst.io/ will be filled with content over time…

Can only be better than the mobile app. :crazy_face:

But I really want the ability to have a custom frontend. To load pre-planned ballots, vote recommendations, …

Do you know if the multiwallet registration I’ve made for fund10 is supposed to work for fund11? (In the catalyst town halls, they said that we had nothing to do if we were already registered for fund10 but when I check my voting power on adastat it doesn’t find anything…)

This doesn’t say anything about the general case, but I’ve found that my registration done before Fund 10 finds my the correct balance (“voting power”) of my old voting key today.

Here’s the tool I’ve been using to verify it (same as offered for Fund 10, and still marked testnet… I’ve not heard of anything more current or indicating “mainnet”): https://verify.testnet.projectcatalyst.io/

1 Like

Never mind, it does work on Adastat, I was using the wrong QR code from a prior single wallet registration I forgot to delete after successfully generating a multi registration qr code… My old phone is displaying the correct voting power but my new phone on which I just readded the qr code is not for some reason but the wallet ID is the same on both phone so it must just be a matter of time.

2 Likes