How to add a Cardano Stake Pool Owner

Lambda Honeypot
9 min readMar 28, 2021

Pre-requisites:

  • A secure air-gapped offline machine running linux
  • An existing delegator you want to make a pool owner. They must have their recovery phrase for the wallet they used to delegate
  • Access to your block producing node to update the registration certificate / pledge

After setting up our Cardano Stakepool we wanted to add an additional pool owner so we could increase the pledge. The tricky part is that we wanted to convert an existing delegated stake account to an owner. Here is how we did it!

DISCLAIMER

This process requires software to be installed on an offline air-gapped machine and the new pool owner entering their mnemonic recovery phrase. THIS NEEDS TO BE DONE SAFELY.

If your new pool owner cannot complete Step 1 below then you will have to setup the environment for them and have them physically enter their recovery phrase. THIS REQUIRES THEM TO TRUST YOU — it would be very easy for a malicious actor to steal the new owner’s ADA if they can access more than the stake keys extracted in Step 1.

Also these steps are followed AT YOUR OWN RISK. If you are unsure of what these steps are doing or are not comfortable validating them yourself then DO NOT PROCEED. The authors accept no liability for any loss or harm caused by following these directions.

Step 1: Safely extracting stake vkey and skey

This is possibly the most difficult part as it requires some amount of technical skill for the new pool owner OR them being able to physically access the SPO’s offline environment.

First we need to install software in to a safe (offline) environment and generate the necessary files. This is sometimes known as the “Mnemonic Method” and credit goes to Ilap for creating it (we found it on the CoinCashew guide so credit to them too).

We need to download the cardano wallet software. You can go to this link or use this command to download:

wget https://hydra.iohk.io/build/3662127/download/1/cardano-wallet-shelley-2020.7.28-linux64.tar.gz

Next verify this is the correct software version by going to the download page, clicking on details and copying the SHA-256 number here:

Now open a terminal and run the following command in the directory you downloaded the tar.gz file to, replacing <SHA-HASH> with the value of the SHA-256 hash you got from the above link.

echo “<SHA-HASH> *cardano-wallet-shelley-2020.7.28-linux64.tar.gz” | shasum -a 256 — check

This should return something like:

cardano-wallet-shelley-2020.7.28-linux64.tar.gz: OK

If this does not look right ensure you have repeated the above steps correctly, otherwise it may not be safe to continue!

Next you need to create the script to extract the files you will need. You can do this by opening an editor and pasting the following:

#!/bin/bashCADDR=${CADDR:=$( which cardano-address )}
[[ -z "$CADDR" ]] && ( echo "cardano-address cannot be found, exiting..." >&2 ; exit 127 )
CCLI=${CCLI:=$( which cardano-cli )}
[[ -z "$CCLI" ]] && ( echo "cardano-cli cannot be found, exiting..." >&2 ; exit 127 )
OUT_DIR="$1"
[[ -e "$OUT_DIR" ]] && {
echo "The \"$OUT_DIR\" is already exist delete and run again." >&2
exit 127
} || mkdir -p "$OUT_DIR" && pushd "$OUT_DIR" >/dev/null
shift
MNEMONIC="$*"
# Generate the master key from mnemonics and derive the stake account keys
# as extended private and public keys (xpub, xprv)
echo "$MNEMONIC" |"$CADDR" key from-recovery-phrase Shelley > root.prv
cat root.prv |"$CADDR" key child 1852H/1815H/0H/2/0 > stake.xprvcat root.prv |"$CADDR" key child 1852H/1815H/0H/0/0 > payment.xprvTESTNET=0
MAINNET=1
NETWORK=$MAINNET
cat payment.xprv |"$CADDR" key public | tee payment.xpub |"$CADDR" address payment --network-tag $NETWORK |"$CADDR" address delegation $(cat stake.xprv | "$CADDR" key public | tee stake.xpub) |tee base.addr_candidate |"$CADDR" address inspect
echo "Generated from 1852H/1815H/0H/{0,2}/0"
cat base.addr_candidate
echo
# XPrv/XPub conversion to normal private and public key, keep in mind the
# keypars are not a valind Ed25519 signing keypairs.
TESTNET_MAGIC="--testnet-magic 42"
MAINNET_MAGIC="--mainnet"
MAGIC="$MAINNET_MAGIC"
SESKEY=$( cat stake.xprv | bech32 | cut -b -128 )$( cat stake.xpub | bech32)
PESKEY=$( cat payment.xprv | bech32 | cut -b -128 )$( cat payment.xpub | bech32)
cat << EOF > stake.skey
{
"type": "StakeExtendedSigningKeyShelley_ed25519_bip32",
"description": "",
"cborHex": "5880$SESKEY"
}
EOF
cat << EOF > payment.skey
{
"type": "PaymentExtendedSigningKeyShelley_ed25519_bip32",
"description": "Payment Signing Key",
"cborHex": "5880$PESKEY"
}
EOF
"$CCLI" shelley key verification-key --signing-key-file stake.skey --verification-key-file stake.evkey
"$CCLI" shelley key verification-key --signing-key-file payment.skey --verification-key-file payment.evkey
"$CCLI" shelley key non-extended-key --extended-verification-key-file payment.evkey --verification-key-file payment.vkey
"$CCLI" shelley key non-extended-key --extended-verification-key-file stake.evkey --verification-key-file stake.vkey
"$CCLI" shelley stake-address build --stake-verification-key-file stake.vkey $MAGIC > stake.addr
"$CCLI" shelley address build --payment-verification-key-file payment.vkey $MAGIC > payment.addr
"$CCLI" shelley address build --payment-verification-key-file payment.vkey --stake-verification-key-file stake.vkey $MAGIC > base.addr
echo "Important the base.addr and the base.addr_candidate must be the same"
diff base.addr base.addr_candidate
popd >/dev/null

Then saving the file as extractPoolStakingKeys.sh.

Now you will need to copy the extractPoolStakingKeys.sh and cardano-wallet-shelley-2020.7.28-linux64.tar.gz files over to your offline air-gapped machine via USB.

Once copied over you will need to extract the cardano wallet software on the air-gapped machine:

tar -xvf cardano-wallet-shelley-2020.7.28-linux64.tar.gz

Then you need to make the script runnable and add the cardano wallet tools to your path:

chmod +x extractPoolStakingKeys.sh
export PATH=”$(pwd)/cardano-wallet-shelley-2020.7.28:$PATH”

Now you should be able to extract the key files using the mnemonic recovery phrase —REMINDER THAT THIS REQUIRES TRUST!! If the new pool owner cannot do this part of the process themselves then the safest way to do it is for them to physically enter it at your air-gapped machine. THEY WILL HAVE TO TRUST THAT YOU WONT KEEP THEIR KEYS OR RECOVERY PHRASE.

Run this command with the mnemonic recovery phrase like so:

./extractPoolStakingKeys.sh extracted_key_dir/ <15|24-word length mnemonic>

This should create a folder called extracted_key_dir on the offline machine containing multiple files. PAY ATTENTION TO THE OUTPUT — the base.addr and base.addr_candidate files must be the same — if they are not the output should indicate this. If they are not please verify the steps to this point.

Once this is completed successfully you should be able to copy the stake.vkey and stake.skey files out of the extracted_key_dir (we renamed by prefixing ‘new_’ to the files) and delete the directory:

mv extracted_key_dir/stake.vkey new_stake.vkey
mv extracted_key_dir/stake.skey new_stake.skey
rm -rf extracted_key_dir

For added security you should delete the bash_history like so:

echo “” > ~/.bash_history

And log out to ensure that the mnemonic phrase is not cached in memory.

The new files should be securely transferred to the pool operator’s secure air-gapped machine if a difference device was used to extract the files.

It’s worth noting that access to the new_stake.skey and new_stake.vkey should only allow a malicious actor to alter where the stake is delegated and not enable them to transfer funds — so it is fairly safe to give your pool operator access to these files.

Step 2: Adding the pool owner

This is an adaptation of the Coincashew guide so credit goes to them. Also if you used that guide this should be familiar

Firstly ensure the new pool owner keys (new_stake.vkey and new_stake.skey) are copied to the cold key location. We will need to generate delegate certs, pool cert and re-run the pool registration. We assume you have the existing stake/payment/rewards keys, poolMetaDataHash.txt and new stake keys in the same directory on the offline air-gapped machine.

Next create the registration pool.cert like so on your offline air-gapped machine:

cardano-cli stake-pool registration-certificate \
--cold-verification-key-file node.vkey \
--vrf-verification-key-file vrf.vkey \
--pool-pledge 200000000 \
--pool-cost 340000000 \
--pool-margin 0.10 \
--pool-reward-account-verification-key-file stake.vkey \
--pool-owner-stake-verification-key-file stake.vkey \
--pool-owner-stake-verification-key-file new_stake.vkey \
--mainnet \
--single-host-pool-relay <dns based relay, example ~ relay1.ada_pool.com> \
--pool-relay-port 6000 \
--metadata-url <url where you uploaded poolMetaData.json> \
--metadata-hash $(cat poolMetaDataHash.txt) \
--out-file pool.cert

You need to use your own parameters and values where appropriate. This will create an operator fee of 340 ADA, a pledge of 200 ADA and a margin of 10%.

Next you will need to create the deleg.cert for the original stake key on the offline air-gapped machine:

cardano-cli stake-address delegation-certificate \
--stake-verification-key-file stake.vkey \
--cold-verification-key-file node.vkey \
--out-file deleg.cert

And now the new_deleg.cert for the new stakepool owner:

cardano-cli stake-address delegation-certificate \
--stake-verification-key-file new_stake.vkey \
--cold-verification-key-file node.vkey \
--out-file new_deleg.cert

You will need to copy the deleg.cert, new_deleg.cert and pool.cert to your block producing node so we can generate the transaction. Once there run these on the block producing node:

currentSlot=$(cardano-cli query tip --mainnet | jq -r '.slotNo')
echo Current Slot: $currentSlot

This gives us the current slot we need for the transaction. Next verify the ADA balance:

cardano-cli query utxo \
--address $(cat payment.addr) \
--mary-era \
--mainnet > fullUtxo.out
tail -n +3 fullUtxo.out | sort -k3 -nr > balance.outcat balance.outtx_in=""
total_balance=0
while read -r utxo; do
in_addr=$(awk '{ print $1 }' <<< "${utxo}")
idx=$(awk '{ print $2 }' <<< "${utxo}")
utxo_balance=$(awk '{ print $3 }' <<< "${utxo}")
total_balance=$((${total_balance}+${utxo_balance}))
echo TxHash: ${in_addr}#${idx}
echo ADA: ${utxo_balance}
tx_in="${tx_in} --tx-in ${in_addr}#${idx}"
done < balance.out
txcnt=$(cat balance.out | wc -l)
echo Total ADA balance: ${total_balance}
echo Number of UTXOs: ${txcnt}

If the output here doesnt have the expected balance you should double check the steps so far. Next we build the transaction with our two delegate cert files and the new pool cert to verify the fees are ok:

cardano-cli transaction build-raw \
${tx_in} \
--tx-out $(cat payment.addr)+${total_balance} \
--invalid-hereafter $(( ${currentSlot} + 10000)) \
--fee 0 \
--certificate-file pool.cert \
--certificate-file deleg.cert \
--certificate-file new_deleg.cert \
--mary-era \
--out-file tx.tmp

This transaction will be invalid 10000 slots after the currentSlot captured earlier. This means if you do not sign and submit the transaction quickly enough you may need to repeat these steps.

Calculate the transaction fee:

fee=$(cardano-cli transaction calculate-min-fee \
--tx-body-file tx.tmp \
--tx-in-count ${txcnt} \
--tx-out-count 1 \
--mainnet \
--witness-count 3 \
--byron-witness-count 0 \
--protocol-params-file params.json | awk '{ print $1 }')
echo fee: $fee

Build the transaction:

cardano-cli transaction build-raw \
${tx_in} \
--tx-out $(cat payment.addr)+${txOut} \
--invalid-hereafter $(( ${currentSlot} + 10000)) \
--fee ${fee} \
--certificate-file pool.cert \
--certificate-file deleg.cert \
--certificate-file new_deleg.cert \
--mary-era \
--out-file tx.raw

Copy the generated tx.raw file to the air-gapped offline machine and then sign the transaction, including the new pool owners skey:

cardano-cli transaction sign \
--tx-body-file tx.raw \
--signing-key-file payment.skey \
--signing-key-file $HOME/cold-keys/node.skey \
--signing-key-file stake.skey \
--signing-key-file new_stake.skey \
--mainnet \
--out-file tx.signed

Copy the tx.signed file back to the block producing node and submit:

cardano-cli transaction submit \
--tx-file tx.signed \
--mainnet

Now you should be able to see multiple pool owners listed on your pool’s page, for example on adapools.org:

Be patient at this stage, it may take a little while for the information to be propagated to any pool explorers.

Step 3: Increasing the pledge

This is simply a case of repeating Step 2, but with a higher pledge amount! We wont repeat those steps, but we felt it was important to ensure the pool owner is added before increasing the pledge. This may be being over cautious, but it’s how we did it!

Want more?

Feel free to head over to our website https://www.lambda-honeypot.com or come and talk to us on Telegram or follow us on Twitter! Also we would always love more delegates so please consider delegating to #HONEY!

--

--