Private Signer Service
JEP-738 removes AWS KMS authority from the long-running, browser-reachableqtg.main process.
Core registers RemoteSignerProxy; a separate private FastAPI process owns exactly one
AwsKmsEvmSigner and the AWS credential chain.
This implementation is a no-live foundation. Its presence does not authorize deployment,
migration, KMS access, signing, broadcast, or fund movement.
Process boundary
The signer service has no published host port in
infra/docker-compose.stargate-kms-mainnet.yml. Only the private Compose network can reach port
8120. The overlay mounts the AWS profile directory on qtg-signer, never qtg-mainnet.
Lighter secure withdraw remains unregistered under this remote plane. JEP-821 owns remote KMS
backend attestation for that lane; JEP-738 does not weaken its concrete KMS gate.
HTTP surface
POST /signaccepts the current requiredSignRequestand returnsSignResult. Supported formats are movementevm_tx/eip712_digest, strict-SIWEeip191_message, and typed-datahyperliquid_approve_agent_eip712_json(the latter requires a matchingHyperliquidApproveAgentIntent); strict-SIWE requires a matchingMessageSigningIntentand canonicalbase64:original bytes.GET /healthreturns exactlyok,signer_address,chain_families, andprotocol_version.- OpenAPI, Swagger, and ReDoc are disabled.
- Both routes require the same signer-specific nonce-HMAC authentication.
X-QTG-Signer-Version, X-QTG-Signer-Key-Id,
X-QTG-Signer-Timestamp, X-QTG-Signer-Nonce, and X-QTG-Signer-Signature.
The default freshness window is 300 seconds.
For eip191_message, AwsKmsEvmSigner independently decodes the canonical base64 payload,
recomputes SHA-256 and the EIP-191 version 0x45 decimal-byte-length digest, and compares the
message intent before KMS I/O. Opaque digest-only requests, malformed encoding, intent mismatch,
and unsupported local-private-key fallback fail closed.
After MAC verification, the server commits (key_id, nonce) to
public.signer_request_nonces before routing or KMS I/O. The uniqueness constraint is the
multi-process replay arbiter. Missing schema, unavailable PostgreSQL, duplicate nonce, malformed
request, and oversized body all fail closed before signer I/O.
Core configuration
All signer-plane values are required together:MG_REMOTE_SIGNER_AUTH_TOKEN is retired. Any non-empty value fails bootstrap. Before every
/sign, the proxy authenticates /health and requires its address to match
MG_REMOTE_SIGNER_EXPECTED_ADDRESS.
qtg.main rejects non-empty MG_LOCAL_SIGNER_* values and non-empty
MG_EXTRA_SIGNERS_JSON. CEX-only deployments may leave every remote signer value empty.
Signer-service configuration
MG_SIGNER_AWS_ENDPOINT_URL and
MG_SIGNER_KMS_TIMEOUT_SECONDS. Unknown MG_SIGNER_* variables fail startup. Nonce retention must
be at least twice the timestamp tolerance so a future-skewed accepted request cannot become
replayable while its timestamp remains valid.
Startup proves all of the following before the service accepts requests:
- configuration is complete and bounded;
public.signer_request_noncesis queryable;- KMS key metadata/state is valid;
- signer address and EVM chain-family health are valid.
/sign.
Migration and database privilege boundary
Revision0012_signer_request_nonces is additive and global. It creates only the persistent replay
table and timestamp index. A downgrade refuses to erase non-empty nonce evidence.
The repository supplies the schema, not production grants. The deployed signer DB role should be
limited to SELECT, INSERT, and retention DELETE on public.signer_request_nonces plus the
owned sequence. Applying the migration or grants is an operator action outside this document.
Safe verification
Render the topology without starting it:qtg-mainnet has no AWS mount or AWS/KMS inputs, qtg-signer has no ports entry, and
only the signer has the read-only AWS profile mount. Do not run up, call KMS, or send a sign
request merely to validate packaging.
HMAC rotation
The current service accepts one HMAC key only; there is no undocumented dual-key overlap. Coordinate a bounded maintenance window:- stop Core signing dispatch;
- generate a new 32-byte-or-longer secret and a new key ID;
- update both Core and signer configuration;
- restart the signer and confirm startup readiness;
- restart Core and confirm authenticated signer health;
- remove the old secret from the secret manager and record the rotation.
Explicit one-off tools
Reviewed operator CLIs may still constructAwsKmsEvmSigner directly with MG_LOCAL_SIGNER_*.
Those commands retain their existing acknowledgements and live-operation approval boundaries. They
do not run inside qtg.main and do not claim JEP-738 dispatcher durability by themselves.