Skip to main content
This tutorial rotates a staked validator’s consensus key when Cosmos-KMS holds the current key rather than a local file. It continues the remote signing tutorial and reuses that setup: the single-node chain kms-demo-1, with node home ~/.kms-demo-node (RPC on port 26657) and signer home ~/.kms-demo (listening on port 26659). The rotation is the standard zero-downtime rotation with three differences. The new key is generated in a signing backend. The second node gets its own signer process. The new public key comes from the second node’s RPC instead of a key file. For the standard procedure and the rotation rules, see Rotate a consensus key, Staking. Commands use simd as the chain binary and val as the validator key name. Substitute your own for a real chain. The live pair and the new pair run on one host, so the second node and its new signer take spare ports: the second node listens for its signer on port 26669 and serves RPC on port 26667.

Prerequisites

  • A validator already signing through Cosmos-KMS, from the remote signing tutorial, still running.
  • All prerequisites of the standard rotation: Cosmos SDK 0.55 and CometBFT 0.40 or later, the target key type in consensus params, no rotation in the current unbonding period, and funds for the burned rotation fee plus gas. See Rotate a consensus key, Staking.
  • jq and curl, used to derive the new public key.
  • Spare ports on the host for the second node and its signer.

1. Generate the new key in a backend

For an HSM or AWS KMS, generate the key with the backend’s own tooling. The commands are in Configure a signing backend. This guide uses the file backend, so a scratch simd init generates a fresh key file.
For a post-quantum mldsa65 target on the file backend, add --consensus-key-algo ml_dsa_65 to the simd init command above and set algorithm: mldsa65 in the signer’s keys block in step 2. See Configure a signing backend for more info on PKCS#11 and AWS KMS backends.
The new key is ~/.kms-demo-scratch/config/priv_validator_key.json. The rest of the scratch home is disposable.
The new key must be freshly generated. Reusing key material any signer has signed with risks a double sign, which tombstones the validator.

2. Configure a second signer with the new key

One Cosmos-KMS process cannot hold two keys for the same chain. The new key must run in its own process, with its own home and double-sign state:
Copy the new key to the second signer’s home:
Replace the contents of ~/.kms-demo2/kms.yaml with the following. The addr is the port the second node listens on for its signer. For an HSM or AWS KMS key, the keys block instead binds the backend entry from step 1:
For a post-quantum mldsa65 keys, set algorithm: mldsa65 in the signer’s keys block above.
Do not start the signer yet. It starts in step 3, right before the second node, so its connection retries are still fast when the node comes up.

3. Bring up the second node and its signer

Initialize a fresh node home and give it the chain’s genesis:
On a real chain with history, a fresh node takes days to sync from genesis. Use state sync or a snapshot to reach the chain head quickly. See State sync for more info.
With the prep done, start the new signer and the second node in quick succession. In one terminal, start the signer. It logs dial failed and retries until the node exists:
Right away, in a second terminal, start the second node. The flags point it at the new signer on port 26669. Move its listeners off the live node’s ports, and peer it with the live node so it syncs:
At startup the second node makes a single attempt, about three seconds long, to reach its signer, then exits with can't get pubkey: ... endpoint connection timed out. Start the signer and the node close together, so the signer, still retrying several times a second, connects inside that window. If the node exits this way, the signer has backed off to 10-second retries: stop the signer, start it again, and start the node right after.
Confirm the second node has caught up. The value is false once it is synced:
Until the rotation applies, the second node follows the chain without signing. Its signer logs served pubkey request but no signatures. That is correct.

4. Derive the new public key and rotate

The second node fetched its key from the new signer at startup and reports it at /status. Read it and reformat it into the proto-JSON the rotation command accepts:
For an ML-DSA key, the proto type differs: use "@type":"/cosmos.crypto.mldsa65.PubKey" in the jq expression instead. Submit the rotation from the operator account and capture the transaction hash:
After the transaction lands in a block, confirm the code is 0:
The burned rotation fee is charged separately from the gas fee above.

5. Verify and retire the old pair

The validator set swaps to the new key atomically two heights after execution. Confirm the set carries only the new key. The value matches $PK, and the old key is gone:
At the swap, CometBFT stops requesting signatures from the old pair on its own. Stop the live node (~/.kms-demo-node) and the live signer (~/.kms-demo) with Ctrl-C in their terminals. Confirm blocks keep flowing through the new pair. Run this twice a few seconds apart and watch the height climb:
Keep the old key in its backend until the unbonding period ends. The validator remains slashable for its past behavior until then.

What can go wrong

  • The new signer exits with app: multiple signers bound to chain: both keys are in one kms.yaml. Run the new key in its own process.
  • The validator is jailed after the rotation and neither signer is signing: the rotation targeted the stray local key. Rotate to the key from the second node’s /status, once the unbonding window allows it.
  • The transaction is rejected: the standard failure modes apply, including the rotation limit and unsupported key types. See Rotate a consensus key, Staking.

Next steps