Files
mpc/docs/INSTALLATION.md

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 a StorageClass for 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 mpcd nodes. Each node listens on a single --listen address (default :9999).
  • Peer discovery. Static. Every node is started with one --peer node<id>@host:port flag per remote node. The cluster size is n = len(--peer) + 1.
  • Threshold. --threshold t is the minimum number of signers required to produce a signature. The daemon refuses to start when t > n. See pkg/transport/registry.go::HasSigningQuorum for 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 standard MPC_BACKUP_S3_* env vars read by pkg/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=legacy flag on mpcd — legacy deployments use the older lux-mpc binary out of cmd/.

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