Skip to content

Key Manager API

The Key Manager API lets you import and remove EIP-2335 encrypted keystores at runtime through the standard Ethereum Key Manager API endpoints at /eth/v1/keystores. No restart required — keys are loaded into memory immediately and available for signing within milliseconds.

This is useful for testing, temporary validators, dynamic key rotation, or any workflow where you need to add and remove keys without touching the filesystem or redeploying the signer.

FilesystemDynamoDB + KMSKey Manager API
StorageDisk (YAML + JSON files)AWS DynamoDB (AES-256-GCM)In-memory only
PersistenceSurvives restartsSurvives restartsLost on restart
MutabilityRead-only at runtimeRead-only at runtime (unless writable enabled)Full add/remove at runtime
Encryption at restEIP-2335 (PBKDF2/Scrypt)KMS-backed Shamir + AES-256-GCMN/A (memory only)
Use caseStandard deploymentsAWS-native, multi-account custodyTesting, temporary validators, dynamic rotation

All three key sources are additive. You can run them simultaneously — the signer merges keys from all configured sources into a single in-memory keystore.

Enable the Key Manager API under key_sources.key_manager_api:

key_sources:
key_manager_api:
enabled: true
max_concurrent_reads: 50
max_concurrent_writes: 10
request_timeout_seconds: 30
max_items_per_request: 100
FieldTypeDefaultDescription
enabledboolfalseEnable the Key Manager API endpoints
max_concurrent_readsint50Maximum concurrent GET and DELETE operations
max_concurrent_writesint10Maximum concurrent POST (import) operations
request_timeout_secondsint30Per-request timeout in seconds
max_items_per_requestint100Maximum keystores per import or pubkeys per delete request

You can also enable it via environment variables:

Terminal window
CONTAINMENT_KEY_SOURCES__KEY_MANAGER_API__ENABLED=true
CONTAINMENT_KEY_SOURCES__KEY_MANAGER_API__MAX_ITEMS_PER_REQUEST=200

Or CLI flags:

Terminal window
containment-chamber --key-manager-api

GET /eth/v1/keystores — Returns all loaded validator keys across all key sources.

Auth scope: list_keys

Terminal window
curl -s http://operator:my-secure-token-1@localhost:9000/eth/v1/keystores | jq

Response:

{
"data": [
{
"validating_pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a",
"readonly": true
},
{
"validating_pubkey": "0xa1d1ad0714035353258038e964ae9675dc0252ee22cea896825c01458e1807bfad2f9969338798548d9858a571f7425c",
"readonly": false
}
]
}

The readonly field indicates whether the key can be deleted via this API:

  • readonly: true — Key was loaded from filesystem or DynamoDB. Cannot be deleted via the Key Manager API.
  • readonly: false — Key was imported via the Key Manager API. Can be deleted.

POST /eth/v1/keystores — Import one or more EIP-2335 encrypted keystores.

Auth scope: import_keystores

The request body contains parallel arrays of keystore JSON strings and their corresponding passwords. Each keystore is decrypted in parallel using a thread pool, and the resulting BLS keypair is loaded into memory.

Terminal window
curl -s -X POST http://operator:my-secure-token-1@localhost:9000/eth/v1/keystores \
-H "Content-Type: application/json" \
-d '{
"keystores": [
"{\"crypto\":{\"kdf\":{\"function\":\"pbkdf2\",\"params\":{\"dklen\":32,\"c\":262144,\"prf\":\"hmac-sha256\",\"salt\":\"...\"},\"message\":\"\"},\"checksum\":{\"function\":\"sha256\",\"params\":{},\"message\":\"...\"},\"cipher\":{\"function\":\"aes-128-ctr\",\"params\":{\"iv\":\"...\"},\"message\":\"...\"}},\"pubkey\":\"93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a\",\"version\":4,\"uuid\":\"...\"}"
],
"passwords": [
"my-keystore-password"
],
"slashing_protection": null
}' | jq

Response:

{
"data": [
{
"status": "imported"
}
]
}

Each entry in the data array corresponds to the keystore at the same index in the request. Possible statuses:

StatusDescription
importedKey was successfully decrypted and loaded
duplicateKey is already loaded (from any source) — no action taken
errorDecryption failed or password was missing

DELETE /eth/v1/keystores — Remove one or more keystores by public key.

Auth scope: delete_keys

Only keys that were imported via the Key Manager API can be deleted. Keys loaded from the filesystem or DynamoDB are protected and will return an error status.

Terminal window
curl -s -X DELETE http://operator:my-secure-token-1@localhost:9000/eth/v1/keystores \
-H "Content-Type: application/json" \
-d '{
"pubkeys": [
"0xa1d1ad0714035353258038e964ae9675dc0252ee22cea896825c01458e1807bfad2f9969338798548d9858a571f7425c"
]
}' | jq

Response:

{
"data": [
{
"status": "deleted"
}
],
"slashing_protection": "{\"metadata\":{\"interchange_format_version\":\"5\",\"genesis_validators_root\":\"0x0000000000000000000000000000000000000000000000000000000000000000\"},\"data\":[]}"
}

Each entry in the data array corresponds to the pubkey at the same index in the request. Possible statuses:

StatusDescription
deletedKey was removed from memory
not_foundNo key with this public key is loaded
not_activeKey exists but is not active
errorKey exists but was not imported via the Key Manager API (e.g. filesystem or DynamoDB key)

Access to the Key Manager API is controlled by auth_policies. Each endpoint requires a specific scope:

EndpointRequired Scope
GET /eth/v1/keystoreslist_keys
POST /eth/v1/keystoresimport_keystores
DELETE /eth/v1/keystoresdelete_keys

Example policy granting full Key Manager API access:

auth_policies:
operator:
token: "env:OPERATOR_TOKEN"
allowed_scopes:
- list_keys
- import_keystores
- delete_keys

When no auth_policies are configured, all endpoints are open (Web3Signer-compatible behavior).

For full details on auth policies, scopes, and token configuration, see the Signing Auth guide.

  • Ephemeral storage — Keys exist only in memory. A signer restart loses all Key Manager API keys. Use filesystem or DynamoDB for persistent storage.
  • No slashing protection export — The delete endpoint returns an empty EIP-3076 interchange object. Export slashing data from your anti-slashing backend (PostgreSQL, SQLite, DynamoDB) directly if migrating keys.
  • No cross-instance sync — Keys imported on one signer instance are not visible to other instances. Each instance maintains its own in-memory keystore.
  • No key rotation — There is no endpoint to replace a key. Delete and re-import to rotate.
  • Encrypted keystores only — The import endpoint accepts EIP-2335 encrypted keystores (PBKDF2 or Scrypt). Raw hex keys cannot be imported via this API — use filesystem key descriptors instead.
  • Request size limits — Import and delete requests are capped at max_items_per_request (default: 100) to prevent resource exhaustion. Requests exceeding this limit receive a 400 Bad Request response.