priv_validator_laddr listener. TMKMS connects over CometBFT’s SecretConnection, and the Cosmos-KMS tcp:// transport is the same, so the listener works unchanged.
Prerequisites
- A validator currently signing through TMKMS, with access to its
tmkms.tomland state file. - jq, used to translate the state file in step 4.
- Cosmos-KMS installed and initialized with
kms init. Remote signing tutorial covers installation, which needs Go 1.26 or later, make, and git.
1. Translate the config
Createkms.yaml and translate each block from your tmkms.toml:
A standard cosmos-sdk node has no peer ID on its
priv_validator_laddr listener, so there is usually no <node-id>@ prefix to carry over, and both TMKMS and Cosmos-KMS use a bare tcp://host:port.
Below is a complete kms.yaml example for a single validator on the file backend:
keys block shown is the file backend. Its fields differ per backend, which step 2 covers. It also needs keys[].algorithm, which has no tmkms.toml equivalent, so set it explicitly (ed25519 for a softsign key), as the example shows. The configuration reference lists every field.
2. Move the consensus key into a backend
Get the consensus key into the backend named in yourkeys block. The path depends on where TMKMS holds it today.
From softsign
The softsign backend keeps the key in a file, but the format differs from what the Cosmos-KMS file backend reads. A TMKMS softsign key is the base64-encoded 32-byte ed25519 seed, while the file backend expects a CometBFTpriv_validator_key.json or the base64-encoded 64-byte ed25519 key. Pointing key_file at a softsign key directly fails with expected 64-byte ed25519 key, got 32.
If you still have the validator’s original priv_validator_key.json (TMKMS softsign was imported from it), point key_file at that file directly. No conversion is needed.
From only the softsign key (the file named by the path in your [[providers.softsign]] block), expand the 32-byte seed into the 64-byte seed||pubkey form the file backend accepts. Replace tmkms_softsign.key in the command with that file:
key_file at converted.key. The command needs Python 3 with the cryptography package (pip install cryptography).
From YubiHSM or another HSM
Most HSM-held keys do not move. A YubiHSM, a Fortanix device, or any HSM that exposes the key over PKCS#11 works with the Cosmos-KMSpkcs11 backend directly: wire up a keys block against the same module and keep signing with the same key. Keys generated inside an HSM are typically non-exportable, which is the point of an HSM, so this no-move path is the normal case. For the PKCS#11 config, see Configure a signing backend.
When you are changing custodian rather than keeping the key in place, migrate by rotation: generate a new key in the target custodian and rotate the validator to it on chain, which retires the TMKMS-held key entirely. See Rotate a consensus key, Staking.
Ledger-held consensus keys are not currently supported in Cosmos-KMS. If you need it, open a feature request so demand for it can be gauged.
3. Stop TMKMS
Stop TMKMS and confirm the process is gone. The validator misses blocks until Cosmos-KMS takes over. That gap is expected: missed blocks are recoverable, a double sign from two live signers is not. Keep it short to avoid downtime jailing.Never run TMKMS and Cosmos-KMS at the same time against the same validator key. Each keeps its own last-signed state, so together they can sign the same height, which is a double sign. Stop one fully before starting the other, in both directions, including any rollback.
4. Translate the double-sign state
TMKMS and Cosmos-KMS both track the last signed height, round, and step per chain, and the protection only works if the new signer starts at or above the old signer’s high-water mark. The two store this state differently, so it needs translating. Point the following command at the TMKMS state file named bystate_file in your [[chain]] block (tmkms_state.json is a placeholder). It writes the translated state into the Cosmos-KMS location:
jq expression makes two conversions: round from string to number (the source of the int32 error otherwise), and step + 1 to remap TMKMS’s 0/1/2 signing steps to CometBFT’s 1/2/3.
5. Start Cosmos-KMS
Start Cosmos-KMS with the completedkms.yaml. It dials the validator and resumes signing.
6. Confirm signing resumed
Confirm the chain is producing again and the signer’s state file is advancing. Run the status check twice; a climbing height means signing resumed:<home>/state/<chain-id>.json should also advance past the last height TMKMS signed.
What can go wrong
- Both signers briefly alive: the double-sign risk above. Cut over with the old process confirmed dead, not just signaled.
- The validator stays dark after cutover: the new signer is not reaching the listener. Check the
validators[].addrtranslation and the firewall between the hosts. dial tcp: lookup node-id@host: no such host: the<node-id>@prefix was left invalidators[].addr. Drop it and keep only host and port.file: parse key file "<path>": expected 64-byte ed25519 key, got 32: a TMKMS softsign key was pointed at directly. See the key section.chain "<chain-id>": reload sign-state: json: cannot unmarshal string into Go value of type int32: the TMKMS state file was reused as is. Translate it first; see step 4.
Next steps
- Harden the new setup. See Remote signing best practices.
- Full field reference for the translated config. See the configuration reference.