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.
Comparison
Section titled “Comparison”| Filesystem | DynamoDB + KMS | Key Manager API | |
|---|---|---|---|
| Storage | Disk (YAML + JSON files) | AWS DynamoDB (AES-256-GCM) | In-memory only |
| Persistence | Survives restarts | Survives restarts | Lost on restart |
| Mutability | Read-only at runtime | Read-only at runtime (unless writable enabled) | Full add/remove at runtime |
| Encryption at rest | EIP-2335 (PBKDF2/Scrypt) | KMS-backed Shamir + AES-256-GCM | N/A (memory only) |
| Use case | Standard deployments | AWS-native, multi-account custody | Testing, 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.
Configuration
Section titled “Configuration”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| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Enable the Key Manager API endpoints |
max_concurrent_reads | int | 50 | Maximum concurrent GET and DELETE operations |
max_concurrent_writes | int | 10 | Maximum concurrent POST (import) operations |
request_timeout_seconds | int | 30 | Per-request timeout in seconds |
max_items_per_request | int | 100 | Maximum keystores per import or pubkeys per delete request |
You can also enable it via environment variables:
CONTAINMENT_KEY_SOURCES__KEY_MANAGER_API__ENABLED=trueCONTAINMENT_KEY_SOURCES__KEY_MANAGER_API__MAX_ITEMS_PER_REQUEST=200Or CLI flags:
containment-chamber --key-manager-apiList Keys
Section titled “List Keys”GET /eth/v1/keystores — Returns all loaded validator keys across all key sources.
Auth scope: list_keys
curl -s http://operator:my-secure-token-1@localhost:9000/eth/v1/keystores | jqResponse:
{ "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.
Import Keystores
Section titled “Import Keystores”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.
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 }' | jqResponse:
{ "data": [ { "status": "imported" } ]}Each entry in the data array corresponds to the keystore at the same index in the request. Possible statuses:
| Status | Description |
|---|---|
imported | Key was successfully decrypted and loaded |
duplicate | Key is already loaded (from any source) — no action taken |
error | Decryption failed or password was missing |
Delete Keystores
Section titled “Delete Keystores”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.
curl -s -X DELETE http://operator:my-secure-token-1@localhost:9000/eth/v1/keystores \ -H "Content-Type: application/json" \ -d '{ "pubkeys": [ "0xa1d1ad0714035353258038e964ae9675dc0252ee22cea896825c01458e1807bfad2f9969338798548d9858a571f7425c" ] }' | jqResponse:
{ "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:
| Status | Description |
|---|---|
deleted | Key was removed from memory |
not_found | No key with this public key is loaded |
not_active | Key exists but is not active |
error | Key exists but was not imported via the Key Manager API (e.g. filesystem or DynamoDB key) |
Authentication
Section titled “Authentication”Access to the Key Manager API is controlled by auth_policies. Each endpoint requires a specific scope:
| Endpoint | Required Scope |
|---|---|
GET /eth/v1/keystores | list_keys |
POST /eth/v1/keystores | import_keystores |
DELETE /eth/v1/keystores | delete_keys |
Example policy granting full Key Manager API access:
auth_policies: operator: token: "env:OPERATOR_TOKEN" allowed_scopes: - list_keys - import_keystores - delete_keysWhen 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.
Limitations
Section titled “Limitations”- 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 a400 Bad Requestresponse.