III-1 Payment and stake keys
The wallet that pays the deposits and receives the rewards.
1 Build synced node tx.raw
USB
2 Sign air-gapped machine tx.signed
USB
3 Submit synced node on chain
Build online, sign offline, submit online.
Air-gapped machine cd ~/cold-keys
cardano-cli latest address key-gen \
--verification-key-file payment.vkey \
--signing-key-file payment.skey
cardano-cli latest stake-address key-gen \
--verification-key-file stake.vkey \
--signing-key-file stake.skey
cardano-cli latest address build \
--payment-verification-key-file payment.vkey \
--stake-verification-key-file stake.vkey \
--mainnet \
--out-file payment.addr
cardano-cli latest stake-address build \
--stake-verification-key-file stake.vkey \
--mainnet \
--out-file stake.addr
Copy payment.addr, stake.addr and the .vkey files to a node. Send a small test amount, then at least 505 ADA plus your pledge:
Block producer or relay cardano-cli latest query utxo --address $(cat payment.addr) --output-json
Check The UTxO list shows your amount. Compare the address character by character with the one on the air-gapped machine.
Next: Register the stake address →
III-2 Register the stake address
2 ADA deposit — build, sign, submit.
Block producer or relay cardano-cli latest query protocol-parameters --out-file protocol.json
cardano-cli latest stake-address registration-certificate \
--stake-verification-key-file stake.vkey \
--key-reg-deposit-amt $(jq '.stakeAddressDeposit' protocol.json) \
--out-file stake.cert
cardano-cli latest transaction build \
--tx-in $(cardano-cli latest query utxo --address $(cat payment.addr) --output-json | jq -r 'keys[0]') \
--change-address $(cat payment.addr) \
--certificate-file stake.cert \
--witness-override 2 \
--out-file tx.raw
Air-gapped machine cardano-cli latest transaction sign \
--tx-body-file tx.raw \
--signing-key-file payment.skey \
--signing-key-file stake.skey \
--mainnet \
--out-file tx.signed
Block producer or relay cardano-cli latest transaction submit --tx-file tx.signed
Note keys[0] spends the first UTxO. Too small? Name another one in --tx-in.
Check Transaction successfully submitted; a few minutes later query stake-address-info --address $(cat stake.addr) lists the address.
Next: Pool keys and the operational certificate →
III-3 Pool keys and the operational certificate
Cold keys stay offline; the block producer gets three files.
Air-gapped machine never online · 2 encrypted backups cold.skeycold.counterpayment.skeystake.skeyBlock producer read-only for the node kes.skey90 days vrf.skeyalso in backup node.certPublic safe anywhere *.vkeypayment.addrstake.addrpool IDtx.raw · tx.signed
Where each key lives.
Air-gapped machine cd ~/cold-keys
cardano-cli latest node key-gen \
--cold-verification-key-file cold.vkey \
--cold-signing-key-file cold.skey \
--operational-certificate-issue-counter-file cold.counter
cardano-cli latest node key-gen-VRF \
--verification-key-file vrf.vkey \
--signing-key-file vrf.skey
cardano-cli latest node key-gen-KES \
--verification-key-file kes.vkey \
--signing-key-file kes.skey
The current KES period:
Block producer or relay slotsPerKESPeriod=$(jq -r '.slotsPerKESPeriod' /etc/cardano/shelley-genesis.json)
slotNo=$(cardano-cli latest query tip | jq -r '.slot')
echo $(( slotNo / slotsPerKESPeriod ))
Air-gapped machine cardano-cli latest node issue-op-cert \
--kes-verification-key-file kes.vkey \
--cold-signing-key-file cold.skey \
--operational-certificate-issue-counter-file cold.counter \
--kes-period <kes-period> \
--out-file node.cert
Copy kes.skey, vrf.skey, node.cert to the block producer:
Block producer sudo mkdir -p /var/lib/cardano/keys
sudo install -o cardano -g cardano -m 400 kes.skey vrf.skey node.cert /var/lib/cardano/keys/
shred -u kes.skey vrf.skey
sudo systemctl daemon-reload && sudo systemctl restart cardano-node
Next: Register the pool →
III-4 Register the pool
Metadata, two certificates, the 500 ADA deposit.
Publish poolMetaData.json at a URL of at most 64 characters (homepage or GitHub Pages). Ticker: 3 to 5 characters.
File {
"name": "Your Pool Name",
"description": "What your pool stands for",
"ticker": "TICK",
"homepage": "https://yourpool.example"
}
Block producer or relay cardano-cli latest stake-pool metadata-hash \
--pool-metadata-file <(curl -s -L https://yourpool.example/poolMetaData.json) \
--out-file poolMetaDataHash.txt
jq '.minPoolCost, .stakePoolDeposit' protocol.json
With poolMetaDataHash.txt on the air-gapped machine — amounts in lovelace (1 ₳ = 1,000,000): 1,000 ₳ pledge, 170 ₳ fixed cost, 1 % margin, three relays:
Air-gapped machine cardano-cli latest stake-pool registration-certificate \
--cold-verification-key-file cold.vkey \
--vrf-verification-key-file vrf.vkey \
--pool-pledge 1000000000 \
--pool-cost 170000000 \
--pool-margin 0.01 \
--pool-reward-account-verification-key-file stake.vkey \
--pool-owner-stake-verification-key-file stake.vkey \
--single-host-pool-relay relay1.yourpool.example --pool-relay-port 6000 \
--single-host-pool-relay relay2.yourpool.example --pool-relay-port 6000 \
--single-host-pool-relay relay3.yourpool.example --pool-relay-port 6000 \
--metadata-url https://yourpool.example/poolMetaData.json \
--metadata-hash $(cat poolMetaDataHash.txt) \
--mainnet \
--out-file pool.cert
cardano-cli latest stake-address stake-delegation-certificate \
--stake-verification-key-file stake.vkey \
--cold-verification-key-file cold.vkey \
--out-file deleg.cert
Block producer or relay cardano-cli latest transaction build \
--tx-in $(cardano-cli latest query utxo --address $(cat payment.addr) --output-json | jq -r 'keys[0]') \
--change-address $(cat payment.addr) \
--certificate-file pool.cert \
--certificate-file deleg.cert \
--witness-override 3 \
--out-file tx.raw
Air-gapped machine cardano-cli latest transaction sign \
--tx-body-file tx.raw \
--signing-key-file payment.skey \
--signing-key-file stake.skey \
--signing-key-file cold.skey \
--mainnet \
--out-file tx.signed
Block producer or relay cardano-cli latest transaction submit --tx-file tx.signed
Caution Relay names in the certificate, never the block producer. Keep the pledge in the owner wallet — below it, the pool earns nothing that epoch.
Next: Check the registration →
III-5 Check the registration
Your pool ID and its stake.
Air-gapped machine cardano-cli latest stake-pool id --cold-verification-key-file cold.vkey --output-format bech32 > stakepoolid.txt
cat stakepoolid.txt
Block producer or relay cardano-cli latest query stake-snapshot --stake-pool-id $(cat stakepoolid.txt)
Check The ID starting with pool1 shows up on cexplorer.io and adastat.net . Stake counts after two epoch boundaries.
Sources and license
Text, pictures and order are BRIAN's. Commands for system setup, the firewall, Mithril and the service file are adapted from the Cardano Developer Portal ; every cardano-cli command was written for and run against cardano-cli 11.2.3. The node's own documentation always takes precedence.
Developer Portal license (MIT) — Copyright (c) 2021 Cardano Foundation
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.