Skip to content

Architecture Overview

Containment Chamber is a single statically-linked Rust binary that acts as an Ethereum remote signer. It exposes a Web3Signer-compatible HTTP API, enforces EIP-3076 slashing protection before every signature, and loads validator keys from multiple sources. A separate metrics server runs on port 3000 for Prometheus scraping.

Diagram

A signing request follows this path through the system:

  1. Validator client sends POST /api/v1/eth2/sign/{pubkey} with a JSON body describing the signing operation (attestation, block proposal, etc.)
  2. Backpressure layers apply load shedding, concurrency limiting, and a 30-second timeout — if the system is overloaded, the request is rejected with 503 before reaching any handler
  3. Axum router matches the path and dispatches to the signing handler
  4. AuthPolicy extracts the HTTP Basic auth token, computes its HMAC-SHA256 hash, and checks scope, key, and operation permissions — unauthorized requests get 401/403
  5. Signing handler parses the JSON body into a SigningRequestOwned variant, validates the public key format, and delegates to EthereumSigner
  6. Signing semaphore limits concurrent signing operations to prevent resource exhaustion
  7. Network guard verifies the request’s genesis validators root matches the configured network — rejects cross-network requests
  8. Anti-slashing check calls check_and_update() on the configured backend (PostgreSQL, SQLite, or DynamoDB) — if the operation would cause a slashable offense, it returns 412
  9. BLS signing computes the domain, signing root, and produces the BLS signature using the keypair from the KeyStore
  10. Response returns {"signature": "0x..."} with the hex-encoded BLS signature

The health endpoint (GET /upcheck) bypasses all backpressure layers and always responds, even under heavy load.

Containment Chamber loads validator BLS keypairs from three sources, checked in order at startup:

  • Filesystem — EIP-2335 encrypted keystores (PBKDF2, Scrypt) or raw hex files loaded from one or more directories. Keys are decrypted in parallel using a CPU semaphore. Filesystem keys take precedence on pubkey collision.

  • DynamoDB + KMS — Keys stored in DynamoDB, encrypted with AES-256-GCM using a master key protected by Shamir secret sharing across multiple AWS KMS keys (M-of-N threshold). Supports background key refresh on a configurable interval.

  • Key Manager API — Hot-load and remove EIP-2335 keystores at runtime via GET/POST/DELETE /eth/v1/keystores. Useful for dynamic validator scaling without restarts.

All three sources feed into the same in-memory KeyStore backed by a lock-free DashMap. Keys are wrapped in Arc for cheap concurrent access across async tasks.

When the DynamoDB key source is configured, the signer operates a 6-state seal machine that controls whether signing is permitted. The master key only exists in memory during the Unsealed and AwaitingRotation states — all other states reject signing requests.

The six states are: Uninitialized, Sealed, AwaitingUnseal, Unsealed, AwaitingRotation, and Rotating. Transitions are driven by operator actions (init ceremony, unseal share submissions, seal commands) and by the signer itself (threshold reached, rotation complete).

Filesystem-only deployments bypass the seal machine entirely. The state machine only activates when key_sources.dynamodb is configured.

For the full state diagram, transition rules, and operational procedures, see Seal & Unseal.