* test(zapserver): fix e2e seed/assert mismatch (secret-value vs sk_live_real) TestConsensusE2E_ValidatorReadsSecret seeded "secret-value" but asserted "sk_live_real" — the E2E read path was correct, the assertion constant disagreed with the seed. Align the seed to the asserted live-secret value so the green test actually guards the consensus read path. Co-authored-by: Hanzo Dev <dev@hanzo.ai> * feat(zapserver): expose enveloped secret+sign plane over HTTP /v1/sdk Decomplect the verify->authorize->dispatch core so the ZAP wire and a new HTTP /v1/sdk transport share ONE implementation (Server.dispatch). Add: - POST /v1/sdk/secrets: single RPC endpoint. The op is read from the SIGNED env.Op field, never the URL, so no framing can escalate a read identity into a write. Maps status byte -> HTTP code; replay masked as generic 403 'forbidden' (nonce ledger unprobeable); 4 MiB body cap. - OpSign (0x0050, write/operator) + OpVerify (0x0051, read/validator): deliberate, documented widening of the authorizer. Dispatch to a narrow SignBackend that delegates to luxfi/mpc t-of-n; KMS holds no key material. nil backend -> clear 'signing not configured'. 18 new httptest end-to-end tests: validator read / operator write splits, replay/stale/tamper/oversize/malformed/unknown-op rejection, and sign delegation gated BEFORE the backend (forbidden sign never reaches MPC). All prior authz tests stay green. Co-authored-by: Hanzo Dev <dev@hanzo.ai> * feat(sdksign): keys.Manager -> zapserver.SignBackend adapter Sign delegates to the MPC t-of-n cluster (KMS holds no full key). Verify is a local public-key check: ed25519 (corona) via stdlib crypto/ed25519 (real, tested with a live keypair); secp256k1 (bls) returns a precise ErrVerifyBLSDelegated — a documented capability boundary (secp256k1 verification is owned by the chain/precompile layer), not a fake. Tests (CGO=0, no MPC daemon needed): sign routes to the correct wallet per scheme, result propagates; ed25519 valid verifies + tampered sig/message do NOT; bls verify hits the delegated boundary; unknown validator errors. Co-authored-by: Hanzo Dev <dev@hanzo.ai> * fix(gomod): realign age@v1.5.0 + keys@v1.1.0 go.sum to re-tagged hashes Both tags were force-moved (vendor/docs sync lineage, per LLM.md); go.sum held the pre-re-tag zip hashes, so any GOWORK=off / clean-cache build (CI) failed 'checksum mismatch' before compiling. Updated the two h1 lines to the authoritative direct-fetch hashes; the GOWORK=off build re-verifies the downloaded bits against these, which is the proof they are correct. Never bypassed the check. Co-authored-by: Hanzo Dev <dev@hanzo.ai> * feat(kms): mount /v1/sdk enveloped secrets+sign surface in kmsd Construct the zapserver.Server once from the loaded REK and back BOTH transports with ONE consensus authorizer + ONE nonce ledger: the HTTP /v1/sdk surface (SDK-facing) and the in-cluster ZAP wire. The authorizer and ledger are built whenever the REK is present (fail-closed: refuse to boot without them), independent of ZAP_PORT — so /v1/sdk is available even when the ZAP wire is disabled. Wire the MPC-backed sign/verify via sdksign.New(mgr) when a vault is configured; nil otherwise. Co-authored-by: Hanzo Dev <dev@hanzo.ai> * docs(llm): document /v1/sdk enveloped secrets+sign surface Co-authored-by: Hanzo Dev <dev@hanzo.ai> --------- Co-authored-by: Hanzo Dev <dev@hanzo.ai>
KMS
MPC-backed key management service for the Lux Network. Manages validator keys, threshold signing, and key rotation using distributed MPC (Multi-Party Computation).
No legacy fork. No PostgreSQL. Pure Go server backed by MPC for threshold cryptography and a JSON file store for metadata.
Architecture
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Client │─────▶│ KMS │─────▶│ MPC │
│ (ATS / BD) │ HTTP │ Go Server │ HTTP │ CGGMP21 / │
│ │ │ :8080 │ │ FROST │
└─────────────┘ └──────┬──────┘ └─────────────┘
│
┌──────▼──────┐
│ JSON Store │
│ keys.json │
└─────────────┘
How it works
- KMS is the HTTP API layer — manages validator key lifecycle
- MPC is the crypto backend — performs distributed key generation (DKG) and threshold signing
- Store is a JSON file — maps validator IDs to MPC wallet IDs (no database needed)
Key types
| Key | Protocol | Curve | Use |
|---|---|---|---|
| BLS | CGGMP21 | secp256k1 | Consensus signing (BLS aggregation) |
| Corona | FROST | ed25519 | Ring signatures, post-quantum prep |
Integration with Hanzo Base
KMS runs as a standalone service alongside Hanzo Base services (ATS, BD, TA). In production:
┌──────────────────────┐
│ hanzoai/gateway │
└──────────┬───────────┘
│
┌────────────────────┼────────────────────┐
│ │ │
┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐
│ ATS │ │ BD │ │ TA │
│ (Base) │ │ (Base) │ │ (Base) │
│ :8090 │ │ :8091 │ │ :8092 │
└─────┬─────┘ └─────┬─────┘ └─────┬─────┘
│ │ │
└────────────────────┼────────────────────┘
│
┌─────▼─────┐ ┌───────────┐
│ KMS │──────▶│ MPC │
│ :8080 │ │ :8081 │
└───────────┘ └───────────┘
Base services call KMS for:
- Transit encrypt/decrypt — field-level encryption with per-customer DEKs
- Validator key management — generate, sign, rotate validator keys
- MPC signing — threshold signing for cross-chain bridge operations
IAM Integration
KMS authenticates callers via Hanzo IAM JWT tokens. The Authorization: Bearer <token> header is validated against the IAM JWKS endpoint. No API keys, no separate auth system.
API
POST /api/v1/keys/generate Generate validator key set (BLS + Corona via MPC DKG)
GET /api/v1/keys List all validator key sets
GET /api/v1/keys/{id} Get validator key set by ID
POST /api/v1/keys/{id}/sign Sign message with BLS or Corona key
POST /api/v1/keys/{id}/rotate Rotate (reshare) keys with new threshold/participants
GET /api/v1/status KMS + MPC cluster status
GET /healthz Health check
Generate keys
curl -X POST http://kms:8080/api/v1/keys/generate \
-H 'Content-Type: application/json' \
-d '{"validator_id": "node-0", "threshold": 3, "parties": 5}'
Sign with BLS
curl -X POST http://kms:8080/api/v1/keys/node-0/sign \
-H 'Content-Type: application/json' \
-d '{"key_type": "bls", "message": "base64-encoded-message"}'
Configuration
| Env Var | Flag | Default | Description |
|---|---|---|---|
KMS_LISTEN |
--listen |
:8080 |
HTTP listen address |
MPC_URL |
--mpc-url |
http://mpc-api.lux-mpc.svc.cluster.local:8081 |
MPC daemon URL |
MPC_TOKEN |
--mpc-token |
- | MPC API auth token |
MPC_VAULT_ID |
--vault-id |
- | MPC vault ID (required) |
KMS_STORE_PATH |
--store |
/data/kms/keys.json |
Key metadata store path |
Running
# Build
go build -o kms ./cmd/kms
# Run (requires MPC daemon running)
./kms --vault-id=<your-vault-id> --mpc-url=http://localhost:8081
Kubernetes
KMS deploys as a Deployment (not StatefulSet — metadata is on a PVC or ConfigMap):
apiVersion: apps/v1
kind: Deployment
metadata:
name: kms
spec:
replicas: 2
template:
spec:
containers:
- name: kms
image: us-docker.pkg.dev/<project>/backend/kms:main
ports:
- containerPort: 8080
env:
- name: MPC_VAULT_ID
valueFrom:
secretKeyRef:
name: kms-secrets
key: MPC_VAULT_ID
K8s Operator
The k8-operator/ directory contains a Kubernetes operator that syncs KMS-managed secrets into K8s Secret resources. CRDs:
KmsSecret— pull secrets from KMS into K8s SecretsKmsDynamicSecret— ephemeral secrets with TTLKmsPushSecret— push K8s Secrets to KMS
Related
- luxfi/mpc — MPC daemon (CGGMP21 + FROST)
- luxfi/hsm — Hardware Security Module abstraction
- hanzoai/base — Go application framework (ATS, BD, TA use this)
- hanzoai/iam — Identity and Access Management
License
MIT — see LICENSE