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
| Component | Key material | Notes |
|---|---|---|
| Nitro Enclave (validator's AWS account) | Identity key, plaintext, memory only | Decrypted at boot via KMS Decrypt with a Nitro attestation document. Never logged, never serialized. |
| EC2 host filesystem (validator's AWS account) | KMS-encrypted envelope | The 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 key | Key policy conditions on kms:RecipientAttestation:PCR0/PCR1/PCR2. A modified enclave image gets AccessDenied and refuses to start. |
| Host-proxy (validator's EC2 host) | None | Terminates TLS, verifies the lease JWT, enforces rate limits, forwards over vsock. |
| Marketplace | ES256 JWT signing key, held in AWS KMS | Used via kms:Sign only. Issues short-lived lease tokens, never touches the validator key. |
| Trader SDK | A short-lived JWT | Nothing 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.
| Property | Value |
|---|---|
| Algorithm | ES256 (ECDSA P-256) via AWS KMS kms:Sign, DER converted to JOSE with low-S normalization. KMS has no Ed25519, hence ES256. |
| TTL | Default 600 s (MARKETPLACE_JWT_TTL_SECS), rejected if 0 or above 3600 |
| Claims minted | iss, sub, aud, val_pk, lease_id, epoch_range, exp, iat, nbf, jti, optional quota.max_handshakes_per_min |
| Claims the TEE enforces | aud, 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 stamping | Exclusive 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 rotation | Tokens 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 mapping | aud 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:
- Kill switch. If this release version is revoked, every RPC returns
FAILED_PRECONDITIONwithversion_revoked, including health. - Per-IP fallback limiter. 100 requests/min with burst 20, hardcoded and not operator-configurable. Catches abuse before any JWT work.
- JWT verification. Signature,
exp,nbf,aud,val_pkas above. - Per-lease quota. A token bucket keyed on
lease_id, with the JWTquotaclaim overriding the configured default. Keying onlease_idmeans every shared-mode co-tenant gets an independent bucket. - Payload cap. 162 bytes maximum before anything crosses the vsock.
- 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_issuancerow (lease_id,jti,expires_at). The token itself is never stored. - Revocations and admin actions: written to the marketplace
audit_logtable. The lease-revoke endpoint returns the resultingaudit_log_id. - Operator actions:
validator-cliroutes state-changing commands, includingemergency-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:
- 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. - Emergency stop.
validator-cli emergency-stoprevokes 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. - Signed revocation manifest (kill switch). Every host-proxy polls the
provider repo's GitHub Releases for
revocation.jsonplus its detached signature every 60 seconds, over HTTPS only. If its own version is listed withaction: "halt", every RPC starts returningFAILED_PRECONDITIONversion_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
The full threat matrix, defense layers, and trust boundaries, with implemented-versus-planned status per item.
How to reach us privately, what counts as a vulnerability, and how to verify release artifacts.
A standalone security whitepaper is a planned deliverable. Until it ships, the threat model page above is the canonical public security reference.