Skip to content

API Concepts

How the Web3Signer-compatible, chamber, auth, and key-management APIs fit together

Containment Chamber exposes six logical API surfaces on a single HTTP server. Understanding how they relate — and how a request moves from the network boundary to a signature — makes the configuration and auth model easier to reason about.

Surface Route Prefix Purpose
Signing /api/v1/eth2/* Web3Signer-compatible BLS signing and key discovery
Key Manager /eth/v1/keystores EIP-3076 hot keystore loading and removal
Chamber Operations /api/v1/chamber/status, /api/v1/chamber/seal, /api/v1/chamber/attestation Status, break-glass seal, and attestation
Chamber Keys /api/v1/chamber/keys/* DynamoDB-backed key generation, import, and lifecycle
Auth /api/v1/auth/* Runtime policy and token management
Health /upcheck Liveness probe — no auth required

Metrics are served on a separate port (default 3000) and are not part of the main API surface.

All chamber, signing, key-management, and auth routes participate in policy-based auth evaluation. The Key Manager API uses the same auth engine, accessed by validator clients speaking the standard EIP-3076 protocol.

Use the API Reference for the exact endpoint list, request schemas, response schemas, and error responses for each surface.

  1. HTTP ingress

    Axum receives the request. Tower middleware applies backpressure (LoadShedLayerConcurrencyLimitLayerTimeoutLayer). If the queue is full, the client receives 503 before any auth or signing logic runs.

  2. Token extraction

    The Authorization header is parsed. Bearer tokens are primary; HTTP Basic is accepted as a compatibility shim (username ignored, password treated as the token). No header → unauthenticated request.

  3. CIDR check

    If the token is CIDR-bound, the source IP is checked against the allowed ranges. Failure returns 403.

  4. Policy evaluation

    The evaluator loads the token’s bound policies and checks each rule against the request:

    • Does the route’s required scope match an allowed scope?
    • Does the requested public key match an allowed key list?
    • Does the requested signing operation match an allowed operation list?

    Matching rules use deny-overrides-allow semantics. Any matching deny rejects the request; otherwise at least one matching allow is required. If no rule matches, the default is deny.

  5. Signing pipeline (signing routes only)

    • Network guard validates fork_info against the configured network. Mismatch → 400.
    • Anti-slashing checks the request against the protection database. Slashable → 412.
    • Key lookup finds the private key. Missing → 404.
    • BLS signing produces the signature and returns it.

Tokens are created at runtime and never stored in plaintext. The server only persists the HMAC-SHA256 hash of the token secret.

The operator CLI is the normal way to manage tokens. If you are integrating directly, the API Reference documents the token-create, lookup, renew, and revoke schemas.

  1. Creation

    A management token calls POST /api/v1/auth/tokens. The server generates a random secret, derives an accessor, hashes the secret for storage, and returns the plaintext token once. It is never retrievable again.

  2. Usage

    The client sends the token as Authorization: Bearer <token>. The server hashes the presented value and compares it against stored hashes.

  3. Expiry

    Tokens with a ttl_seconds > 0 expire automatically. The server rejects expired tokens with 401.

  4. Revocation

    A management token calls DELETE /api/v1/auth/tokens/<accessor>. The stored hash is removed; the token is immediately invalid.

Scopes are coarse-grained capabilities that map to route families. They are not fine-grained HTTP-method permissions — a scope grants access to an entire functional area.

Scope Grants access to
sign All signing routes (/api/v1/eth2/sign/*)
public_keys Key discovery (/api/v1/eth2/publicKeys)
list_keys EIP-3076 keystore listing (/eth/v1/keystores)
import_keystores EIP-3076 keystore import (/eth/v1/keystores)
delete_keys EIP-3076 keystore deletion (/eth/v1/keystores)
chamber_status Seal state and health details
chamber_seal Break-glass seal operation
chamber_keys_list Chamber key inventory
chamber_keys_generate DynamoDB key generation
chamber_keys_import Runtime key import
chamber_keys_patch Activate / deactivate chamber keys
chamber_keys_delete Remove chamber keys

Why scopes instead of HTTP methods? Because the API is resource-oriented, not CRUD-oriented. A validator client needs sign and public_keys — it does not care about GET vs POST. Scopes express intent, not mechanics.

For the precise endpoint-to-scope mapping, use the API Reference. This page explains the model; Scalar is the source of truth for path-level details.

The chamber lifecycle is fully automatic — there are no operator ceremony steps, no passphrases, no quorum, and no init/unseal commands. Custody is handled at boot by the static ceremony configuration and AWS KMS.

The chamber has exactly two states:

direction: right
unsealed: Unsealed {
shape: rectangle
style.fill: "#d4edda"
}
sealed: Sealed {
shape: rectangle
style.fill: "#f8d7da"
}
unsealed -> sealed: Break-glass seal\n(SEAL_OVERRIDE latch) {style.stroke: "#dc3545"}
  • Unsealed — signer is operational; master key is resident in memory.
  • Sealed — break-glass latch (SEAL_OVERRIDE) is present; terminal until restart.

There is no Sealed → Unsealed transition. Recovery from Sealed requires an IAM DeleteItem on the SEAL_OVERRIDE row followed by a restart of every replica.

On every boot the background watcher inspects DynamoDB. A fresh chamber auto-inits directly to Unsealed:

Fresh table (no MASTER_KEY row):

The server auto-initializes:

  1. Generates a new master key.
  2. Shamir-splits it M-of-N across the custody KMS keys in the ceremony configuration.
  3. KMS-wraps each share; runs a recovery self-test (attested KMS decrypt + Shamir-combine + commitment check) before any durable write.
  4. Writes the MASTER_KEY row to DynamoDB.
  5. Creates the root token, age-encrypts it to the ceremony.root_token_recipients, and stores it in the ROOT_TOKEN_BOOTSTRAP row.
  6. Transitions to Unsealed.

Existing MASTER_KEY row:

The server auto-unseals:

  1. Attested KMS decrypt of each share.
  2. Shamir-combines the threshold set; verifies the master-key commitment and share-binding HMAC.
  3. Transitions to Unsealed.

SEAL_OVERRIDE latch present at boot:

Transitions directly to Sealed (terminal).

The KMS key set, Shamir threshold, and root-token recipients come from the compiled-in, PCR0-measured CEREMONY static on Nitro builds, or the ceremony: config block on non-Nitro builds. They are never supplied by an operator at runtime.

On a non-Nitro build, the ceremony: block looks like:

ceremony:
generation: 1
kms_threshold: 2 # Shamir M (must decrypt M of N shares)
kms_keys: # N custody keys; each entry is one logical (multi-Region) key
- arns: ["arn:aws:kms:us-east-1:123456789012:key/aaa", "arn:aws:kms:us-west-2:123456789012:key/aaa-replica"]
- arns: ["arn:aws:kms:us-east-1:123456789012:key/bbb"]
- arns: ["arn:aws:kms:us-east-1:123456789012:key/ccc"]
retired_kms_keys: [] # old keys kept decryptable across a rotation; empty in steady state
root_token_recipients: # age X25519 recipients — the root-token bootstrap is encrypted to any-of these
- "age1qz…"

A Nitro build rejects this block; the compiled PCR0-measured static is authoritative.

Rotation is declarative. To rotate custody keys or recipients:

  1. Update the ceremony: block (or redeploy a new Nitro image with the updated compiled static): add new kms_keys, adjust kms_threshold, move old keys to retired_kms_keys, bump generation.
  2. Redeploy.

After unseal, the background watcher reconciles the MASTER_KEY row — generation-gated, single DynamoDB transaction, proves the new keys decrypt before retiring the old. There are no rotate verbs or CLI rotation commands.

containment-chamber operator seal (scope chamber_seal) is a fleet-wide break-glass operation:

  1. Writes the SEAL_OVERRIDE latch to DynamoDB (HMAC’d by a master-key subkey).
  2. Zeroizes the local master key and clears the auth plane.
  3. Every replica’s background watcher observes the latch and seals itself. Boot also honours the latch.

Recovery: IAM DeleteItem on the SEAL_OVERRIDE row (PK SEAL_OVERRIDE), then restart every replica. There is no unseal endpoint and no auto-resume.

For operational runbooks, see Seal & Unseal Operations.

containment-chamber operator attestation (GET /api/v1/chamber/attestation, scope chamber_status) returns the NSM attestation document on Nitro builds. The custody parameters are PCR0-measured, so the attestation proves what ceremony static the enclave is running.

Policies use deny-overrides-allow semantics:

  1. Deny overrides allow — any matching deny rule rejects the request, even if another rule or policy would allow it.
  2. Default deny — if no rule matches, the request is rejected.
  3. Scope, key, operation filters — rules can narrow by any combination.

Example: a policy with two rules

{
"name": "example",
"rules": [
{ "effect": "deny", "operations": ["VOLUNTARY_EXIT"], "keys": ["0xabc..."] },
{ "effect": "allow", "scopes": ["sign", "public_keys"] }
]
}

A VOLUNTARY_EXIT request for 0xabc... is denied because the deny rule matches first. An ATTESTATION request for 0xabc... is allowed because the deny rule does not match and the allow rule does.

Anonymous requests are denied unless static_auth.anonymous references named policies that allow them. This keeps the evaluator deny-by-default while still letting stateless deployments grant token-less access from the config file.

Mode static_auth.anonymous Anonymous request
Stateless Not configured Rejected with 401
Stateless Configured Checked against the referenced named policies
State-backed Not configured Rejected with 401
State-backed static_auth set Invalid config; startup rejects it

State-backed deployments use runtime policies and tokens from the Auth API. Stateless deployments cannot persist those, so they declare auth in config via static_auth — named policies, an optional anonymous binding, and static tokens.