Skip to content

cosmos/kms

Repository files navigation

kms

kms (github.com/cosmos/kms) is an external remote signer for the Cosmos ecosystem. It is conceptually similar to tmkms, but implemented entirely in Go and building directly on top of the CometBFT libraries. It dials out to one or more validator nodes, authenticates each connection with either CometBFT's SecretConnection protocol or the libp2p Noise transport (selected per-validator via the address scheme), and serves Ed25519 consensus signing requests (votes, proposals, and vote extensions) with mandatory per-chain double-sign protection.

The longer-term goal is to be a single remote-signing service for the Cosmos stack: CometBFT consensus (privval) signing today, plus remote signing for IBC relaying and attestation.


Status

Scope Status
Ed25519 consensus signing (votes, proposals, vote extensions) Supported
file key backend (file-based, in-memory Ed25519) Supported
pkcs11 key backend (HSM / token, Ed25519) Supported
cometp2p transport (TCP + SecretConnection) Supported
Multi-chain, multi-validator support Supported
Double-sign protection (reuses CometBFT FilePV state machine) Supported
Dial-out + automatic exponential-backoff reconnect Supported
awskms key backend (AWS KMS, Ed25519) Supported
libp2p transport (Noise) Supported
Account / raw-bytes / ECDSA signing Planned
ML-DSA / eth_secp256k1 key types Planned

How it works

  • Dial-out model. kms dials out to the validator's privval listener (priv_validator_laddr in config.toml) rather than listening itself. This removes the need to expose any port on the KMS host.
  • SecretConnection authentication. Each connection is authenticated and encrypted using CometBFT's SecretConnection protocol. kms uses a dedicated Ed25519 identity key (distinct from the consensus signing key) to authenticate itself to the validator.
  • Request serving. Once connected, the KMS handles PubKey, SignVote, SignProposal, and Ping requests using privval.DefaultValidationRequestHandler.
  • Double-sign protection. Signing is delegated to CometBFT's FilePV state machine, which persists the last-signed height/round/step to a per-chain state file and refuses to sign any regression. This survives process restarts.
  • Automatic reconnect. When a connection drops (validator restart, network hiccup), kms reconnects with capped exponential backoff (200 ms initial, 10 s ceiling) without any manual intervention.

Build

Requires Go 1.25 or newer. Build via the Makefile (the binary is written to build/kms):

make build            # build/kms
make install          # install to GOBIN

Quick start

1. Initialise the home directory

kms init --home ~/.kms

This creates:

  • ~/.kms/kms.yaml — a stub configuration file.
  • ~/.kms/identity.json — a fresh Ed25519 identity key for the SecretConnection.

2. Edit the config

Open ~/.kms/kms.yaml and fill in the real values (see the example config below).

3. Place the consensus key file

Copy or symlink the priv_validator_key.json for each chain to the path you set as key_file in a keys entry (the default file backend).

4. Configure the validator

In the validator's config.toml enable the remote signer listener:

priv_validator_laddr = "tcp://0.0.0.0:26659"

The address must be reachable from the host running kms, and it must match the addr you set in a validators entry.

5. Start the KMS

kms start --home ~/.kms

kms will dial the validator and begin serving signing requests. It logs each connection and any signing errors to stdout.


Configuration reference

chains

Declares one blockchain. You need exactly one chains entry per chain you want to sign for.

Field Type Required Description
id string yes The chain-id string (e.g. cosmoshub-4).
state_file string no Path to the double-sign state file. Defaults to <home>/state/<id>.json. Relative paths are resolved against --home.

validators

Declares one outbound connection to a validator node. A single chain can have multiple validators entries (e.g. primary + backup nodes).

Field Type Required Description
chain_id string yes Must match a declared chains[].id.
addr string yes Address of the validator's privval listener. Use tcp://host:port for the standard SecretConnection transport, or noise://<validator-peer-id>@host:port for the libp2p Noise transport (see libp2p Noise transport).
identity_key string yes Path to the Ed25519 identity key file used to authenticate the SecretConnection. Relative paths are resolved against --home. Use the file generated by kms init.
reconnect bool no Whether to reconnect automatically after a dropped connection. Defaults to true.

keys

A flat list of signing keys. Each entry binds one key to one or more chains via chain_ids and selects its custodian via backend. The remaining fields depend on the chosen backend — fields belonging to other backends are ignored. A chain must be backed by exactly one key.

Fields common to every key:

Field Type Required Description
chain_ids list of strings yes Chain IDs this key signs for. Each must match a declared chains[].id, and each chain may be backed by only one key.
backend string no Custodian: file (default), pkcs11, or awskms.
algorithm string no Key algorithm. Defaults to ed25519 (the only supported value today). Consumed by the pkcs11 and awskms backends.

backend: file

A file-based Ed25519 private key loaded into memory.

Field Type Required Description
key_file string yes Path to the key file. Accepts either a CometBFT priv_validator_key.json (typed JSON with a "priv_key" field) or a file containing the raw base64-encoded 64-byte Ed25519 private key. Relative paths are resolved against --home.

backend: pkcs11

An Ed25519 key on a PKCS#11 token or HSM. The private key never leaves the token: signing is performed on-device via CKM_EDDSA.

Field Type Required Description
module string yes Path to the PKCS#11 module shared library (e.g. /usr/lib/softhsm/libsofthsm2.so). Relative paths are resolved against --home.
token_label string one of token_label/slot CKA_LABEL of the token to use.
slot integer one of token_label/slot Slot number of the token to use. Mutually exclusive with token_label.
key_label string at least one of key_label/key_id CKA_LABEL of the key object.
key_id string (hex) at least one of key_label/key_id Hex-encoded CKA_ID of the key object.
pin string exactly one PIN source User PIN, inline.
pin_env string exactly one PIN source Name of an environment variable holding the user PIN. Preferred over inline.
pin_file string exactly one PIN source Path to a file containing the user PIN (trailing whitespace trimmed). Relative paths resolved against --home.

Provision the key with your HSM tooling before starting kms; the KMS only uses an existing key, it does not generate or import keys. The key must be an Ed25519 (CKK_EC_EDWARDS) signing key. Example using pkcs11-tool with SoftHSM2:

softhsm2-util --init-token --free --label comet --pin 1234 --so-pin 4321
pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so --login --pin 1234 \
  --keypairgen --key-type EC:edwards25519 --label validator --id 01

Example key (PIN supplied via environment, keeping it out of the config file):

keys:
  - chain_ids: [cosmoshub-4]
    backend: pkcs11
    module: /usr/lib/softhsm/libsofthsm2.so
    token_label: comet
    key_label: validator
    key_id: "01"
    pin_env: KMS_PIN
    # algorithm defaults to "ed25519"

backend: awskms

An Ed25519 key stored in AWS KMS. The private key never leaves KMS: signing is performed by the KMS Sign API using the ECC_NIST_EDWARDS25519 key spec and the ED25519_SHA_512 (PureEd25519) algorithm. AWS credentials are resolved by the standard AWS default credential chain (environment variables, shared config/credentials files, SSO, or an IAM instance/container role) — no secrets are placed in the kms config.

Field Type Required Description
key_id string yes KMS key identifier: a key id, full key ARN, or an alias (alias/<name>).
region string no AWS region of the key. Falls back to the AWS default chain (e.g. AWS_REGION) when omitted.
profile string no Shared-config profile name to use. Falls back to the AWS default chain when omitted.
endpoint string no Custom KMS endpoint URL. Intended for LocalStack / testing; leave unset for real AWS.

Provision the key with your AWS tooling before starting kms; the KMS only uses an existing key, it does not create or import keys. The key must be an asymmetric ECC_NIST_EDWARDS25519 key with key usage SIGN_VERIFY. Example using the AWS CLI:

aws kms create-key --key-spec ECC_NIST_EDWARDS25519 --key-usage SIGN_VERIFY
aws kms create-alias --alias-name alias/validator --target-key-id <key-id>

Example key (region pinned, credentials from an IAM role):

keys:
  - chain_ids: [cosmoshub-4]
    backend: awskms
    key_id: alias/validator
    region: us-east-1
    # algorithm defaults to "ed25519"

grpc — SignerService gRPC mode

Adding a grpc block enables a TLS gRPC SignerService that runs alongside the privval dial-out signer. Both transports are served from a single kms start process; you can also run grpc-only (no chains or validators entries required when there is no privval workload).

What the service does

  • Sign. Only RecoverableMessage payloads are supported. The service computes SHA-256(message) and returns a RecoverableMessageSignature with fields r, s, and v (a 65-byte recoverable ECDSA signature; v is the 0/1 recovery id). All other Sign payload types return Unimplemented.
  • GetKey / GetKeys. Return { id, pubkey, algo } for one or all registered keys. pubkey is the 33-byte compressed secp256k1 public key; algo is secp256k1.

Authentication and authorisation

The server performs no caller authentication or authorization: any client that can reach the listener may use any configured key. Access must be restricted entirely by transport and network controls — TLS is mandatory (set tls_cert and tls_key in the grpc block), and the listener should be reachable only from trusted callers (e.g. via network policy).

grpc config-key reference

Field Type Required Description
listen string yes host:port the gRPC server binds to (e.g. 0.0.0.0:9090).
tls_cert string yes Path to the TLS server certificate file.
tls_key string yes Path to the TLS server private key file.

grpc.key config-key reference

Field Type Required Description
id string yes Logical key identifier returned by GetKey/GetKeys.
key_file string yes Path to the hex-encoded 32-byte secp256k1 private key file.

Example grpc block

grpc:
  listen: 0.0.0.0:9090
  tls_cert: server.crt
  tls_key: server.key
  keys:
    - id: attestor-1
      key_file: attestor_secp256k1.hex   # hex-encoded 32-byte secp256k1 key

Example config

# ~/.kms/kms.yaml

chains:
  - id: cosmoshub-4
    # state_file defaults to <home>/state/cosmoshub-4.json when omitted

validators:
  - chain_id: cosmoshub-4
    addr: tcp://10.0.0.1:26659
    identity_key: identity.json   # relative to --home

keys:
  - chain_ids: [cosmoshub-4]
    backend: file
    key_file: /secrets/priv_validator_key.json

Multi-chain example:

chains:
  - id: cosmoshub-4
  - id: osmosis-1

validators:
  - chain_id: cosmoshub-4
    addr: tcp://10.0.0.1:26659
    identity_key: identity.json
  - chain_id: osmosis-1
    addr: tcp://10.0.0.2:26659
    identity_key: identity.json

keys:
  - chain_ids: [cosmoshub-4]
    backend: file
    key_file: /secrets/cosmoshub_priv_validator_key.json
  - chain_ids: [osmosis-1]
    backend: file
    key_file: /secrets/osmosis_priv_validator_key.json

libp2p Noise transport

The libp2p Noise transport is an alternative to the default SecretConnection (tcp://) channel. Both sides use the same TCP port and listener — the address scheme (noise:// vs tcp://) is what selects which handshake is performed. No libp2p switch, host, or gossip network is involved; it is a direct TCP connection secured by the Noise_XX handshake.

The key difference from SecretConnection is pinned peer IDs on both sides. SecretConnection uses an ephemeral, unpinned key on the validator's listener, which means the KMS cannot verify it is talking to the right validator. With the Noise transport, each side asserts a stable libp2p identity derived from its existing keys (the validator's node key, the KMS's identity.json), and each side refuses any connection from an unexpected peer.

Obtaining the two peer IDs

KMS peer ID (give this to the validator operator so they can pin it in priv_validator_laddr):

kms peer-id --home ~/.kms

This reads identity.json from <home> and prints the corresponding libp2p peer ID.

Validator peer ID (give this to the KMS operator so they can pin it in validators[].addr):

cometbft show-node-id --libp2p --home ~/.cometbft

This prints the libp2p peer ID derived from the validator's node key.

KMS configuration

Set addr to a noise:// URI that embeds the validator's peer ID:

validators:
  - chain_id: cosmoshub-4
    addr: noise://12D3KooW...validatorPeerID...@10.0.0.1:26659
    identity_key: identity.json   # reused as the KMS's libp2p identity

The identity_key field serves double duty: it authenticates the SecretConnection channel when tcp:// is used, and it provides the KMS's libp2p identity (peer ID) when noise:// is used. No additional key file is needed.

Validator configuration

Set priv_validator_laddr in the validator's config.toml to a noise:// URI that embeds the KMS peer ID:

priv_validator_laddr = "noise://12D3KooW...kmsPeerID...@0.0.0.0:26659"

The validator uses its node key (node_key.json) as its Noise identity. Any incoming connection whose authenticated peer ID does not match the pinned KMS peer ID is rejected before any signing request is served.

Mutual pinning requirement

Both sides must configure the other's peer ID:

  • The KMS encodes the validator's peer ID in the noise:// address it dials — the handshake fails immediately if the remote key does not match.
  • The validator encodes the KMS's peer ID in priv_validator_laddr — any connection from a different peer is dropped.

There is no way to disable peer-ID pinning when using noise://; it is enforced unconditionally.

Key types

Ed25519 and secp256k1 keys are supported. Consensus keys and node keys in CometBFT are Ed25519, so no extra setup is needed.

Security notes

  • the file backend is NOT for production custody. The private key is loaded from disk and held in process memory in plaintext for the lifetime of the process. Use the pkcs11 backend (or the awskms backend) for production environments where the key must never leave secure hardware.
  • The identity key is not the consensus signing key. identity.json authenticates the SecretConnection channel; it does not sign consensus messages and does not need to be protected to the same degree as the priv_validator_key.json.
  • Double-sign protection is per state file. The state file records the highest height/round/step that has been signed. kms refuses to sign any message that would regress this high-water mark. Never run two kms instances against the same validator with different (or missing) state files — doing so removes the double-sign protection.
  • Validator listener exposure. The validator's priv_validator_laddr binds a TCP port. Ensure it is not reachable from untrusted networks (use a firewall or a private VLAN between the validator and the KMS host).

Testing

make test             # go test ./... -count=1
make test-race        # with the race detector

Repository layout

Public packages (importable by external consumers, e.g. an IBC relayer):

kms/
├── config/               # YAML config types (Config, Key, Backend) + validation; embeddable
├── signing/              # Public signing contracts: Backend (consensus) and Signer/Key (service)
│   ├── file/             # File-based Ed25519 / secp256k1 keys
│   ├── pkcs11/           # PKCS#11 / HSM Ed25519 keys (+ pkcs11test helpers)
│   └── awskms/           # AWS KMS Ed25519 keys
└── gen/signerservice/    # Generated SignerService protobuf/gRPC types

Internal packages (the kms daemon; not part of the public API):

kms/
├── cmd/
│   └── kms/              # Binary entrypoint; CLI subcommands (version, init, start, peer-id)
└── internal/
    ├── version/          # Version string; overridable at link time via -ldflags
    ├── identity/         # Identity key load/generate (wraps CometBFT p2p.NodeKey)
    ├── signer/           # ChainSigner: double-sign protection + PrivValidator impl (consumes signing.Backend)
    ├── signerservice/    # SignerService gRPC server (consumes signing.Signer/Key)
    ├── manager/          # Manager: supervised dial-out connections with backoff
    └── app/              # Wiring: Build() assembles Manager from a validated Config

About

Support for secure signing solutions in Cosmos

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors