8.8 KiB
MPC Installation Guide
This guide walks an operator from a clean machine to a running mpcd
cluster in consensus mode — the production path the README leads
with: peer-to-peer ZAP transport, embedded ZapDB storage, no external
broker, no service-discovery layer.
If you have an existing deployment running NATS + Consul, see the Legacy transport appendix at the bottom of this document.
Prerequisites
- Go 1.23+ — install instructions
- make (build orchestration)
- For containerised deployments: Docker 24+ with the Compose v2 plugin
- For Kubernetes deployments: a cluster with Kustomize available
(
kubectl kustomize) and aStorageClassfor persistent volumes
Consensus mode has no external broker or service-discovery
dependency. Inter-node messaging, peer registry, and the encrypted
key-share store (ZapDB) are all embedded in the mpcd binary.
Clone and Build
git clone https://github.com/luxfi/mpc.git
cd mpc
make # builds ./bin/mpcd (and ./bin/mpc CLI helpers)
# or
go install ./cmd/mpcd
go install ./cmd/mpc
Available binaries
| Binary | Purpose |
|---|---|
mpcd |
The MPC daemon. One binary, all topologies (standalone, t-of-n cluster). See cmd/mpcd/README.md. |
mpc |
CLI helper for one-shot tasks: generate-peers, generate-identity, register-peers, recovery. |
Consensus Mode Concepts
Consensus mode (the default; selectable explicitly via --mode consensus
in config.yaml) is what the README calls the production path. The
properties an operator needs to know:
- Transport. Peer-to-peer ZAP wire protocol over TLS 1.3 between
mpcdnodes. Each node listens on a single--listenaddress (default:9999). - Peer discovery. Static. Every node is started with one
--peer node<id>@host:portflag per remote node. The cluster size isn = len(--peer) + 1. - Threshold.
--threshold tis the minimum number of signers required to produce a signature. The daemon refuses to start whent > n. Seepkg/transport/registry.go::HasSigningQuorumfor the arithmetic. - Identity. Each node has a long-lived Ed25519 keypair under
<data>/keys/<node-id>_identity.json. It is generated on first start if not present. Inter-node messages are signed and verified with this key. - State. All persistent state — key shares, key metadata, peer
registry, dashboard DB — lives under
--data(default SQLite + ZapDB). Mount a volume here. - Backups. A periodic ZapDB backup runs every
backup_period_seconds(default 300 s) into<data>/backups. Optional S3 upload is configured via the standardMPC_BACKUP_S3_*env vars read bypkg/backup.
Quick Start: 3-Node Local Cluster
The fastest way to see consensus mode work end-to-end on a single host.
1. Pick a threshold
t-of-n with t >= floor(n/2) + 1. For 3 nodes use t = 2.
2. Start each node
Run each block in its own terminal:
# Node 0
mpcd start \
--node-id node0 --listen :9990 --api :9800 \
--data ./data/node0 --threshold 2 \
--peer [email protected]:9991 --peer [email protected]:9992
# Node 1
mpcd start \
--node-id node1 --listen :9991 --api :9801 \
--data ./data/node1 --threshold 2 \
--peer [email protected]:9990 --peer [email protected]:9992
# Node 2
mpcd start \
--node-id node2 --listen :9992 --api :9802 \
--data ./data/node2 --threshold 2 \
--peer [email protected]:9990 --peer [email protected]:9991
3. Verify quorum
curl -s http://127.0.0.1:9800/healthz | jq
Expected response once all peers connect:
{
"status": "healthy",
"mode": "consensus",
"signing_quorum": true,
"ready_count": 3,
"threshold": 2
}
The node reports healthy while ready_count >= threshold + 1,
healthy-reduced while signing quorum holds but a non-critical peer is
unreachable, and degraded (HTTP 503) once quorum is lost. See
cmd/mpcd/main.go::healthHandler for the full contract.
4. Generate a wallet
curl -X POST http://127.0.0.1:9800/keygen \
-H "Authorization: Bearer ${MPC_INTERNAL_API_KEY}" \
-H "Content-Type: application/json" \
-d '{"org_id": "demo", "wallet_id": "w-001"}'
MPC_INTERNAL_API_KEY is required for every internal endpoint except
/healthz. If you do not set it, mpcd derives a deterministic key from
the node identity and logs a warning — fine for local testing, not
acceptable in production.
Docker Compose
The repo ships a minimal single-node compose file at compose.yml for
demos and integration testing:
export JWT_SECRET=$(openssl rand -hex 32)
docker compose up
For multi-node clusters in containers, run one ghcr.io/luxfi/mpc-api
container per node and wire the --peer flags to the other containers'
network addresses. See deployments/onprem/README.md for a worked
example.
Kubernetes
The repo ships a production-grade StatefulSet:
cd k8s && kubectl kustomize . | kubectl apply -f -
k8s/mpc-statefulset.yaml builds the peer list from the StatefulSet
ordinal — each pod resolves its siblings via the headless service
mpc-node-headless.lux-mpc.svc and starts as:
mpcd start --node-id node${ORDINAL} \
--listen :9999 --api :9800 --data /data/mpc --threshold 3 \
--peer [email protected]:9999 \
--peer ...
The manifests include secrets.yaml (KMS-synced), a NetworkPolicy
restricting east-west traffic to the cluster, and an IngressRoute for
the dashboard. The default kustomization is --threshold 3 over 5 pods;
adjust both in mpc-statefulset.yaml if you size differently.
For environment-specific overlays, see deployments/k8s-primary/ and
deployments/k8s-private/.
Configuration Surface (config.yaml)
config.yaml is read by Viper with AutomaticEnv, so every key has an
env-var override (mpc_threshold → MPC_THRESHOLD, etc.). The minimal
production file:
mode: consensus
environment: mainnet # mainnet | testnet | local
# Consensus
mpc_threshold: 2
max_concurrent_keygen: 2
# Storage
db_path: "." # SQLite + ZapDB live here
# Backup
backup_enabled: true
backup_period_seconds: 300
backup_dir: backups
# Event-initiator pubkey (Ed25519, hex). Set when running with an
# explicit initiator service; leave empty inside trusted clusters where
# all messages originate via the internal API.
event_initiator_pubkey: ""
Secrets are never placed in config.yaml in production. Source them
through the HSM password provider:
mpcd start ... \
--hsm-provider aws \
--hsm-key-id arn:aws:kms:us-east-1:...:key/...
In environment: production, mpcd refuses to start with an
env/file provider (see resolveZapDBPassword in cmd/mpcd/main.go).
Operational Hardening
Before declaring a cluster production-ready, work through the Operator Runbook — health-probe spec, key rotation, backup and restore, node add/remove, and the deployment smoke test.
Appendix A: Legacy Transport (NATS + Consul, deprecated)
Deprecated. The legacy NATS + Consul transport is retained only for clusters that have not yet migrated. New deployments must use consensus mode (above). The legacy code path will be removed in a future major version. There is no
--mode=legacyflag onmpcd— legacy deployments use the olderlux-mpcbinary out ofcmd/.
The previous version of this document recommended NATS + Consul as
prerequisites. That guidance was correct for the legacy lux-mpc binary
and still applies if you are operating that path. For posterity:
Legacy prerequisites
- NATS server (JetStream enabled)
- Consul server (for peer registration and service discovery)
Legacy bring-up
docker compose -f compose.legacy.yaml up -d # NATS + Consul
mpc generate-peers -n 3 # peers.json
mpc register-peers # writes peers into Consul
mpc generate-identity --node node0 [--encrypt]
mpc generate-initiator [--encrypt]
cd node0 && lux-mpc start -n node0
The legacy bring-up script (scripts/launch-3-nodes.sh) and the legacy
status helper (scripts/cluster-status.sh) target this path. They will
not work against mpcd consensus deployments. New automation should
target the consensus-mode endpoints documented above.
Migration
A migration path from legacy → consensus is out of scope for this
document; it requires reshare-based share rotation and is tracked in
scripts/migration/. Operators planning a migration should coordinate
with the maintainers.
Appendix B: Decrypt an age-encrypted initiator key
age --decrypt -o event_initiator.key event_initiator.key.age