Getting Started
This page gets you from a fresh checkout to a running local KMS and the
attestation-verifier web app, then walks one challenge → get-key cycle
against your local instance. It is the fastest way to see the moving parts
before you read the flows in depth.
The production KMS only releases keys to nodes running inside a real Intel TDX confidential VM, with quotes signed by the hardware and keys derived over the dstack socket. None of that exists on a laptop, so the KMS ships a dev-only mock path (a Cargo feature, off by default) that lets you exercise the HTTP surface and most of the verification pipeline without TDX hardware.
Prerequisites
Section titled “Prerequisites”- Rust 1.88+ (
rustup,cargo) — the KMS is a Cargo workspace. - Node.js 18+ and npm — for the attestation-verifier web app.
curland (optionally)jq— to drive the endpoints by hand.
Build the KMS
Section titled “Build the KMS”The workspace has a single member, mero-kms-phala. From the repository root:
# Debug build of the whole workspacecargo build
# Or an optimized buildcargo build --releaseThis produces the production binary — it contains no mock-attestation code. To run locally you want the dev build described next.
Run the KMS in dev mode
Section titled “Run the KMS in dev mode”Two independent things stand between a fresh start and a working /get-key
locally, and you have to satisfy both:
- Mock attestation — accept mock TDX quotes instead of requiring real
hardware-signed ones. This is gated behind the default-off
mock-attestationCargo feature, and only then is theACCEPT_MOCK_ATTESTATIONenv var read. - A ready policy —
/get-keyis fail-closed. Until a policy is loaded the KMS returns503 policy_not_ready. Locally, load one from the environment withUSE_ENV_POLICY=trueinstead of fetching a signed release policy.
Build with the feature and set both knobs:
cd mero-kmsACCEPT_MOCK_ATTESTATION=true \USE_ENV_POLICY=true \ cargo run --features mock-attestationOn startup the service logs its resolved configuration. Expect to see the mock
warning, the locked-read-only profile, the in-memory challenge store, and
Policy ready for key issuance: true:
INFO Starting mero-kms-phalaINFO Listen address: 0.0.0.0:8080INFO KMS profile cohort: locked-read-onlyINFO Challenge store backend: in-memoryINFO Policy ready for key issuance: trueWARN WARNING: Mock attestation acceptance is enabled. This should NEVER be used in production!INFO Server listening on 0.0.0.0:8080Change the listen address (optional)
Section titled “Change the listen address (optional)”Defaults are 0.0.0.0:8080 for the HTTP listener and /var/run/dstack.sock for
the dstack socket. Override with LISTEN_ADDR if 8080 is taken:
LISTEN_ADDR=127.0.0.1:9090 \ACCEPT_MOCK_ATTESTATION=true \USE_ENV_POLICY=true \ cargo run --features mock-attestationConfirm it is up
Section titled “Confirm it is up”/health is a pure liveness probe — it answers 200 regardless of policy
readiness:
curl -s http://localhost:8080/health# {"status":"alive","service":"mero-kms-phala"}Walk one challenge → get-key cycle
Section titled “Walk one challenge → get-key cycle”Key release is a two-round challenge-response. Round 1 (/challenge) is fully
self-contained and works locally; round 2 (/get-key) runs the whole
verification pipeline and is where the local/hardware boundary shows up. Both
routes are documented in full in Key Release.
Round 1 — get a challenge
Section titled “Round 1 — get a challenge”Send any base58btc peer ID. The KMS mints a single-use nonce and returns a challenge ID (32 hex characters) plus the base64 nonce and its expiry:
curl -s -X POST http://localhost:8080/challenge \ -H 'content-type: application/json' \ -d '{"peerId":"12D3KooWEyoppNCUx8Yx66oV9fJnriXwCcXwDDUA2kj6vnc6iDEp"}'{ "challengeId": "9f8e7d6c5b4a39281706f5e4d3c2b1a0", "nonceB64": "aGVsbG8td29ybGQtZXhhbXBsZS0zMi1ieXRlcy1ub25jZQ==", "expiresAt": 1753104000}The challenge is valid for CHALLENGE_TTL_SECS (default 60) and can be consumed
exactly once.
Round 2 — redeem it at /get-key
Section titled “Round 2 — redeem it at /get-key”/get-key takes the challenge ID plus a TDX quote, the peer’s public key, and a
signature binding all of it to the peer identity:
curl -s -X POST http://localhost:8080/get-key \ -H 'content-type: application/json' \ -d '{ "challengeId": "9f8e7d6c5b4a39281706f5e4d3c2b1a0", "quoteB64": "<base64 raw TDX quote with the nonce in report_data>", "peerId": "12D3KooWEyoppNCUx8Yx66oV9fJnriXwCcXwDDUA2kj6vnc6iDEp", "peerPublicKeyB64": "<base64 protobuf libp2p public key>", "signatureB64": "<base64 signature over the canonical payload>" }'The KMS then, in order: validates the shapes, consumes the challenge (so a replay always fails on the second try), verifies the peer signature and public-key ownership, verifies the quote (mock quotes are accepted here in dev), enforces the measurement policy (skipped for accepted mocks), and finally derives the key over dstack. On success it returns:
{ "key": "<derived storage encryption key>" }You can still observe the fail-closed behavior directly. Send a /get-key
request with placeholder credentials and watch the gates reject it, and watch
the challenge get consumed even on failure:
# Second identical request returns invalid_challenge — the first attempt# already consumed the single-use challenge, regardless of why it failed.Every error is a machine-readable tag with an HTTP status; the full catalog is in Error handling.
Run the attestation verifier
Section titled “Run the attestation verifier”The verifier is a separate React/Vite app with a serverless /api/verify
backend. Run it locally:
cd attestation-verifiernpm installnpm run devVite serves it at http://localhost:5173. The two routes are /kms (verify a
Phala KMS by URL) and /mero-tee (verify a merod node or KMS); the landing page
redirects to /kms.
Next steps
Section titled “Next steps”- Verify a release — check release assets and use the public verifier UI.
- Attestation flow — where quotes come from and the
64-byte
report_datalayout. - Key release — the full
challenge → get-keyprotocol and every field it checks. - Configuration reference — every environment variable and default.
- Calimero Core: TEE Attestation — the protocol-level view of the same primitive.