Skip to content

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.

  • Rust 1.88+ (rustup, cargo) — the KMS is a Cargo workspace.
  • Node.js 18+ and npm — for the attestation-verifier web app.
  • curl and (optionally) jq — to drive the endpoints by hand.

The workspace has a single member, mero-kms-phala. From the repository root:

Terminal window
# Debug build of the whole workspace
cargo build
# Or an optimized build
cargo build --release

This produces the production binary — it contains no mock-attestation code. To run locally you want the dev build described next.

Two independent things stand between a fresh start and a working /get-key locally, and you have to satisfy both:

  1. Mock attestation — accept mock TDX quotes instead of requiring real hardware-signed ones. This is gated behind the default-off mock-attestation Cargo feature, and only then is the ACCEPT_MOCK_ATTESTATION env var read.
  2. A ready policy/get-key is fail-closed. Until a policy is loaded the KMS returns 503 policy_not_ready. Locally, load one from the environment with USE_ENV_POLICY=true instead of fetching a signed release policy.

Build with the feature and set both knobs:

Terminal window
cd mero-kms
ACCEPT_MOCK_ATTESTATION=true \
USE_ENV_POLICY=true \
cargo run --features mock-attestation

On 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-phala
INFO Listen address: 0.0.0.0:8080
INFO KMS profile cohort: locked-read-only
INFO Challenge store backend: in-memory
INFO Policy ready for key issuance: true
WARN WARNING: Mock attestation acceptance is enabled. This should NEVER be used in production!
INFO Server listening on 0.0.0.0:8080

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:

Terminal window
LISTEN_ADDR=127.0.0.1:9090 \
ACCEPT_MOCK_ATTESTATION=true \
USE_ENV_POLICY=true \
cargo run --features mock-attestation

/health is a pure liveness probe — it answers 200 regardless of policy readiness:

Terminal window
curl -s http://localhost:8080/health
# {"status":"alive","service":"mero-kms-phala"}

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.

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:

Terminal window
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.

/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:

Terminal window
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:

Terminal window
# 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.

The verifier is a separate React/Vite app with a serverless /api/verify backend. Run it locally:

Terminal window
cd attestation-verifier
npm install
npm run dev

Vite 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.