Search docs

Find a documentation page

Security model

What 2QUIC protects, who trusts what, and how the signing oracle, JWT contract, quotas, and revocation paths enforce it.

The guarantee

The validator's Ed25519 identity key exists in plaintext only inside a Nitro Enclave running in the validator's own AWS account, and that enclave signs exactly one thing: TLS 1.3 client CertificateVerify payloads that match Firedancer's fd_keyguard_payload_matches_tls_cv matcher.

Everything else in the system exists to make that sentence hold under attack. The 2QUIC Marketplace sells handshake capacity. It can mint and stop minting access tokens, but it holds no validator key and can never sign anything on a validator's behalf.

Who holds what

ComponentKey materialNotes
Nitro Enclave (validator's AWS account)Identity key, plaintext, memory onlyDecrypted at boot via KMS Decrypt with a Nitro attestation document. Never logged, never serialized.
EC2 host filesystem (validator's AWS account)KMS-encrypted envelopeThe ciphertext at rest: the deploy script writes it to a 0600 file (/etc/staked-quic/seed.bin) on the host. Useless without the KMS key policy being satisfied.
KMS CMK (validator's AWS account)Decrypt gate, not the keyKey policy conditions on kms:RecipientAttestation:PCR0/PCR1/PCR2. A modified enclave image gets AccessDenied and refuses to start.
Host-proxy (validator's EC2 host)NoneTerminates TLS, verifies the lease JWT, enforces rate limits, forwards over vsock.
MarketplaceES256 JWT signing key, held in AWS KMSUsed via kms:Sign only. Issues short-lived lease tokens, never touches the validator key.
Trader SDKA short-lived JWTNothing else.

Three trust zones

The deployment splits into three zones, and each party's trust is deliberately narrow.

Validator AWS account. The enclave and host-proxy run on infrastructure the operator owns. The validator trusts their own AWS account, AWS Nitro attestation, and the open-source enclave and host-proxy code they build and deploy themselves. The key never leaves their account, in any form.

Central marketplace. The API, probe, and settlement workers. The marketplace is never on the signing data path. Validators prove ownership at registration with a signed challenge plus on-chain stake verification, and trust the marketplace only for listing, billing, and token issuance.

Trader infrastructure. The SDK talks gRPC directly to the validator's TEE, presenting the marketplace JWT. The trader trusts the Nitro attestation chain (PCR-gated key release plus the signed release attestation published with each build) to know the enclave runs the published open-source code, and trusts the marketplace to issue tokens only for paid, active leases.

The signing oracle

Before signing, the enclave validates every payload against the same rules as Firedancer's keyguard:

  • total length is 130, 146, or 162 bytes (a 98-byte prefix plus a SHA-256, SHA-384, or SHA-512 transcript hash)
  • bytes 0 to 63 are all 0x20 (the TLS 1.3 pad)
  • bytes 64 to 97 are exactly "TLS 1.3, client CertificateVerify\0". The client context only: the enclave will never sign a server CertificateVerify
  • the transcript hash content is not interpreted, only its length

Anything else returns a typed error without touching the key. The enclave cannot be made to sign a transaction, a vote, or an arbitrary message.

Consequence for a stolen token: a leaked JWT buys rate-limited TLS handshakes against one specific validator until the token expires, at most 10 minutes at the default TTL. It can never produce a transaction or vote signature.

JWT contract summary

The marketplace mints lease tokens from POST /v1/leases/{lease_id}/token (see Authentication). The lease must be active and the current epoch inside its booked range.

PropertyValue
AlgorithmES256 (ECDSA P-256) via AWS KMS kms:Sign, DER converted to JOSE with low-S normalization. KMS has no Ed25519, hence ES256.
TTLDefault 600 s (MARKETPLACE_JWT_TTL_SECS), rejected if 0 or above 3600
Claims mintediss, sub, aud, val_pk, lease_id, epoch_range, exp, iat, nbf, jti, optional quota.max_handshakes_per_min
Claims the TEE enforcesaud, val_pk, lease_id, exp, nbf, quota only. iss, sub, iat, jti, epoch_range are audit metadata, ignored TEE-side to keep the trust surface at exactly what is checked.
Quota stampingExclusive leases get the full configured budget (default 100 handshakes/min). Shared leases get max(1, floor(budget / seats)) so one co-tenant cannot starve the others.
Key rotationTokens carry a kid. The host-proxy trusts a static set of P-256 public keys from its config, so rotation is: add the new key, restart, overlap, retire the old kid.
Error mappingaud or val_pk mismatch returns PERMISSION_DENIED, every other verification failure returns UNAUTHENTICATED.

The token endpoint is rate limited at 12 requests/min per API key, and every minted token writes an issuance audit row.

Runtime enforcement at the host-proxy

Each SignCertificateVerify request passes these gates in order, cheapest and most categorical first:

  1. Kill switch. If this release version is revoked, every RPC returns FAILED_PRECONDITION with version_revoked, including health.
  2. Per-IP fallback limiter. 100 requests/min with burst 20, hardcoded and not operator-configurable. Catches abuse before any JWT work.
  3. JWT verification. Signature, exp, nbf, aud, val_pk as above.
  4. Per-lease quota. A token bucket keyed on lease_id, with the JWT quota claim overriding the configured default. Keying on lease_id means every shared-mode co-tenant gets an independent bucket.
  5. Payload cap. 162 bytes maximum before anything crosses the vsock.
  6. The enclave matcher described above, the final and load-bearing check.

The marketplace only learns aggregate usage: the host-proxy pushes per-lease 1-minute handshake counts to POST /v1/leases/{id}/usage, authenticated by that lease's own JWT. The TEE is invoked only at TLS handshake time, so neither the validator nor the marketplace ever sees transaction content, programs, or trading activity.

Audit logging

  • Token issuance: every minted JWT writes a jwt_issuance row (lease_id, jti, expires_at). The token itself is never stored.
  • Revocations and admin actions: written to the marketplace audit_log table. The lease-revoke endpoint returns the resulting audit_log_id.
  • Operator actions: validator-cli routes state-changing commands, including emergency-stop, through an append-only NDJSON audit log on the operator's machine.
  • Planned: an enclave-side signed audit log and WORM S3 archival are specified in the design doc but not implemented yet.

Signed releases

TEE-side artifacts (the enclave image and host-proxy) ship as GitHub Releases of the open-source provider repo. Each release carries an attestation file (EIF hash, PCR values, host binary hashes) and the revocation list, each with a detached Ed25519 signature under the release signing key, verified strictly over the exact asset bytes. Marketplace platform services deploy as Docker images built in CI from the tagged commit (immutable tags; no downloadable binaries and no SLSA provenance today). Dev-only escape hatches (manifest signature bypass, plaintext gRPC) are compiled out of release builds.

The current release signing key is a placeholder whose private half was destroyed, pending the production key ceremony. Treat release-signature trust accordingly until the production key ships.

The validator dashboard's security panel surfaces this as cosign_verify_status (in v1 a heuristic derived from probe freshness: ok when the validator is live and probed within the last hour, stale or failed otherwise, not yet a cryptographic check) alongside auth_failures_24h, which despite its name counts every failed probe in the trailing 24 hours (timeouts and unreachable TEEs included), not only authentication failures.

Revocation paths

Three independent paths, ordered from routine to catastrophic:

  1. Stop issuing plus TTL expiry. Lease revocation (validator- or admin-initiated) simply stops the marketplace minting new tokens. Nothing is pushed to the TEE. Outstanding JWTs die at exp, so access ends within one TTL, at most 10 minutes at the default, with the per-lease quota capping damage in the meantime. Disabling the marketplace signing key in KMS applies the same logic fleet-wide. See the key rotation runbook.
  2. Emergency stop. validator-cli emergency-stop revokes every ingress rule on the TEE's security group, making it unreachable immediately. It is reversible and non-destructive: the enclave keeps running and the key envelope stays sealed. The command defaults to dry-run so an accidental invocation cannot break ingress. See incident response.
  3. Signed revocation manifest (kill switch). Every host-proxy polls the provider repo's GitHub Releases for revocation.json plus its detached signature every 60 seconds, over HTTPS only. If its own version is listed with action: "halt", every RPC starts returning FAILED_PRECONDITION version_revoked. The revoked flag latches and never clears, so replaying an older signed list cannot un-revoke a node. Boot fails closed: a revoked version, or an unfetchable list, exits immediately.

Where to go next

A standalone security whitepaper is a planned deliverable. Until it ships, the threat model page above is the canonical public security reference.