Architecture
PQ Signer adds a post-quantum ML-DSA signature alongside a custodian's existing ECDSA signature on every transaction. The design pairs a Trusted Execution Environment with a cloud KMS: the TEE holds plaintext key material for the brief windows it is needed, and the KMS acts as the policy engine that decides when that material may be released, gating it on a cryptographic attestation of the TEE's measured code. The current implementation targets AWS Nitro Enclaves and AWS KMS; the same TEE+KMS pattern is intended to extend to additional cloud providers and TEE platforms over time. This page walks through how the signing service is structured, where keys live, what each component is allowed to see, and how a request flows end-to-end.
A signing service split into four trust zones
The whole design is organised around a single rule: plaintext ML-DSA private keys exist only inside a Nitro Enclave, briefly, while a signature is being produced. Everything else, including the parent EC2 instance the enclave runs on, is treated as untrusted for plaintext key material.
That gives four trust zones:
| Zone | Components | Sees plaintext PQ keys? |
|---|---|---|
| Untrusted | Public internet | No |
| Authenticated client | Custodian service, holding an mTLS client certificate | No |
| Semi-trusted parent | Gateway EC2, Nitro host EC2, DynamoDB, IAM credentials | No |
| Trusted enclave | Nitro Enclave vCPU and memory, NSM | Yes, briefly during signing |
Authorization sits with the custodian. By the time a request reaches PQ Signer, the custodian's own policy engine has already approved it and produced its own ECDSA signature on the same message. PQ Signer is a co-signer: it adds an ML-DSA signature, identified by an opaque user_id the custodian supplies. The enclave does no ECDSA verification of its own.
End-to-end flow
The deployment splits the parent into two EC2 instances joined by a private mTLS link. The Gateway holds DynamoDB credentials and no KMS access. The Nitro host holds KMS relay credentials and no DynamoDB access. The enclave talks only to KMS, through a vsock-proxy on the parent.
╭──────────────────────╮
│ Custodian client │
╰──────────┬───────────╯
│ HTTPS POST /v1/pq-signer (mTLS)
▼
╭──────────────────────╮
│ Gateway EC2 │ IAM: dynamodb:* NO kms:*
│ validate, resolve │
│ user_id, DDB I/O │
╰──────────┬───────────╯
│ private mTLS, TCP 8443
▼
╭──────────────────────╮
│ Nitro host EC2 │ IAM: kms:Decrypt, kms:GenerateDataKey (relay)
│ host-agent │
╰──────────┬───────────╯
│ vsock
▼
╭─────────────────────────────────────────────────────────╮
│ KMS ↔ TEE channel │
│ │