> ## Documentation Index
> Fetch the complete documentation index at: https://cosmos-docs-security-release.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Configure a signing backend

> Point Cosmos-KMS at the custodian holding the consensus key: AWS KMS, a PKCS#11 HSM, or a file.

A signing backend is the custodian that holds the validator's consensus key and signs with it. Cosmos-KMS supports three: AWS KMS, a PKCS#11 hardware module, and a file on disk. The `keys` block in `kms.yaml` selects one, and this guide configures each in turn.

## Prerequisites

* A running signer and node, which [Remote signing tutorial](/sdk/next/kms/tutorial-file-backend) sets up. Between backends only the `keys` block changes in `kms.yaml`. The file backend reuses the tutorial's existing key, but the AWS KMS and PKCS#11 backends hold a new consensus key the validator must adopt first. See [Adopt the key on a validator](#adopt-the-key-on-a-validator).
* Per backend: the [AWS CLI](https://aws.amazon.com/cli/) and an AWS account for AWS KMS; your HSM's tooling plus [OpenSC](https://github.com/OpenSC/OpenSC)'s `pkcs11-tool` for PKCS#11, with [SoftHSM2](https://www.opendnssec.org/softhsm/) as a local test rig.
* The chain's binary; the examples use `simd`. To build it and run a node, see [Run a node](/sdk/next/node/run-node).

The `algorithm` field is required for all backends.

## AWS KMS

AWS Key Management Service (KMS) is a managed service that stores cryptographic keys and signs with them on request. With this backend, the consensus key lives in KMS and never leaves it. The signer calls the KMS Sign API to produce each signature. Credentials come from the standard AWS default chain: environment, shared config, SSO, or an IAM role. No secrets enter `kms.yaml`.

This backend signs with any key type Cosmos-KMS supports: `ed25519`, `secp256k1`, `secp256k1eth`, and post-quantum `mldsa65`. Provision the AWS key with the key spec that matches the algorithm, such as `ECC_NIST_EDWARDS25519` for `ed25519` or `ML_DSA_65` for `mldsa65`.

Create an ML-DSA-65 signing key and give it an alias (or skip these and point `key_id` at a key you already have):

```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
aws kms create-key --key-spec ML_DSA_65 --key-usage SIGN_VERIFY
```

```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
aws kms create-alias --alias-name alias/validator --target-key-id <key-id>
```

Then bind it in the `keys` block:

```yaml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
keys:
  - chain_ids: [my-chain-1]
    backend: awskms
    algorithm: mldsa65
    key_id: alias/validator
    region: us-east-1
```

The `key_id` accepts a key ID, a full ARN, or an alias. `region` is optional and falls back to the AWS default chain. Two more optional fields, not shown above, are `profile` (a named shared-config profile) and `endpoint` (for LocalStack-style testing only).

The signer needs only two IAM permissions on the key: `kms:GetPublicKey`, called once at startup, and `kms:Sign`, called per block. `kms:DescribeKey` is not required. Attach a least-privilege policy scoped to the key ARN, not the alias; AWS resolves the alias to the key server-side:

```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["kms:GetPublicKey", "kms:Sign"],
      "Resource": "arn:aws:kms:<region>:<account-id>:key/<key-id>"
    }
  ]
}
```

A freshly created KMS key is a new consensus key. To move an existing validator onto it, rotate the validator to the new key; to stand up a new validator, register it with the new key's public key. See [Rotate a consensus key held in Cosmos-KMS](/sdk/next/kms/rotate-key-remote-signer). Creating the key inside KMS keeps it from ever leaving the service.

## `PKCS#11`

The `PKCS#11` backend keeps the consensus key on a hardware security module (HSM) or token and signs on the device through `PKCS#11`, the standard interface for cryptographic hardware. The key never leaves the module. The signer uses an existing key only, so provision one with your HSM tooling first. The example commands below show how to provision an ed25519 key with SoftHSM2 for testing:

```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
softhsm2-util --init-token --free --label validator-token --pin 1234 --so-pin 4321
```

```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so --login --pin 1234 --keypairgen --key-type EC:edwards25519 --label validator --id 01
```

The `--module` path is platform-specific: Linux uses `/usr/lib/softhsm/libsofthsm2.so`, and macOS Homebrew uses `/opt/homebrew/lib/softhsm/libsofthsm2.so` (`/usr/local/lib/softhsm/...` on Intel). Use the same path for the `module` field below.

Then bind it:

```yaml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
keys:
  - chain_ids: [my-chain-1]
    backend: pkcs11
    algorithm: ed25519
    module: /usr/lib/softhsm/libsofthsm2.so
    token_label: validator-token
    key_label: validator
    pin_env: KMS_PIN
```

The signer enforces three field rules at startup:

* Select the token with exactly one of `token_label` or `slot`.
* Select the key with `key_label`, `key_id` (the hex `CKA_ID`), or both.
* Supply the PIN through exactly one of `pin`, `pin_env`, or `pin_file`.

Prefer `pin_env` or `pin_file`. An inline `pin` puts the PIN in the config file.

Set the PIN in the environment the signer runs in so it matches `pin_env`: `export KMS_PIN=<pin>`. If you are using SoftHSM2 as the test rig, also `export SOFTHSM2_CONF=<path-to-softhsm2.conf>` so the module can find the token.

As with AWS KMS, a key generated in the HSM is a new consensus key. Move an existing validator onto it by rotation, or register a new validator with its public key. See [Rotate a consensus key held in Cosmos-KMS](/sdk/next/kms/rotate-key-remote-signer).

## File

The file backend reads the consensus key from a file on the signer's disk into memory. It is the development and testing backend. The key sits in plaintext, so it is not production custody. The [Remote signing tutorial](/sdk/next/kms/tutorial-file-backend) covers it end to end:

```yaml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
keys:
  - chain_ids: [my-chain-1]
    backend: file
    algorithm: ed25519
    key_file: priv_validator_key.json
```

The `key_file` accepts a CometBFT `priv_validator_key.json` or a raw base64-encoded private key. The file backend also signs post-quantum consensus keys. Generate the key with `simd init <moniker> --consensus-key-algo ml_dsa_65` and bind it:

```yaml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
keys:
  - chain_ids: [my-chain-1]
    backend: file
    algorithm: mldsa65
    key_file: priv_validator_key.json
```

{/* Verification status (mldsa65 per backend): file — run-verified above (2026-07-17). awskms — run-verified 2026-07-20 against real AWS KMS (ML_DSA_65 key signed consensus end to end; see the AWS manual-verification note). pkcs11 — source/owner-confirmed but NOT run-verified: SoftHSM2 has no ML-DSA mechanism, so it needs an ML-DSA-capable HSM. */}

## Adopt the key on a validator

The AWS KMS and PKCS#11 backends generate the key inside the custodian, so it is a new consensus key, not the one your validator already runs. Do not just repoint an existing validator's signer at a fresh key: the node then signs with a key the chain never registered, and the chain rejects its proposals with `invalid proposal signature`. Only the file backend, pointed at the validator's existing `priv_validator_key.json`, skips this step.

Adopt the key one of two ways:

* Existing validator: rotate its consensus key to the new one, which derives the new public key from a shadow node and swaps it in with no downtime. See [Rotate a consensus key held in Cosmos-KMS](/sdk/next/kms/rotate-key-remote-signer).
* New validator: register it with the new key's consensus public key using `gentx --pubkey` (or `create-validator --pubkey`). Read that public key from the node's `/status` while the signer holds the key, the same way the rotation guide derives it.

## Verify any backend

Verification is the same regardless of custodian. Start the signer, start the node, and confirm blocks flow, exactly as in [the tutorial](/sdk/next/kms/tutorial-file-backend):

```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
curl -s localhost:26657/status | jq '.result.sync_info.latest_block_height'
```

## What can go wrong

* The signer rejects the config at startup: both `token_label` and `slot` set, or more than one PIN source. The error names the offending field. A missing `algorithm` on the file backend instead fails with the less specific `file: unknown key type`.
* The signer starts but cannot reach the key: wrong `module` path, wrong `key_id` or alias, or AWS credentials with no permission (denied `kms:GetPublicKey` fails at `kms start` with `awskms: get public key for "<key_id>": <AccessDeniedException>`). These also fail at startup.
* The node exits with a pubkey timeout: the signer is not running or not reachable. Start the signer first. It dials and retries.

## Next steps

* Look up any config field, its type, and its constraints. See the [configuration reference](/sdk/next/kms/configuration-reference).
* Run the whole flow once with the file backend. See [Remote signing tutorial](/sdk/next/kms/tutorial-file-backend).
* Move an existing validator onto a key in a new backend by rotation. See [Rotate a consensus key held in Cosmos-KMS](/sdk/next/kms/rotate-key-remote-signer).
