Files
mpc/cmd/mpcd
zeekay 5ae17289d2 mpcd: gate embedded threshold dispatcher with bearer-token + loopback bind
Red round 5 finding B1. The embedded dispatcher on --threshold-listen
was an anonymous local signing oracle:

  1. Any process on the host could POST `{"method":"frost.sign",...}`
     and obtain signatures with no auth gate (only the API on :9800
     enforced MPC_INTERNAL_API_KEY).
  2. An operator typo `MPC_THRESHOLD_LISTEN=0.0.0.0:7300` exposed it
     cluster-wide.
  3. Any SSRF inside mpcd pivoted into the dispatcher.

Fix mirrors the existing internal-API auth pattern (cmd/mpcd/main.go
line ~456):

  - Auth token: read MPC_THRESHOLD_AUTH_TOKEN; if unset, derive a
    deterministic per-cluster token from the node Ed25519 seed
    (SHA-256(seed || "mpc-threshold-rpc") hex). All nodes share the
    same key without extra config; log warns when derivation is used.
    Server.SetAuthToken installs the gate.
  - Loopback bind: refuse non-loopback addr unless
    MPC_THRESHOLD_ALLOW_REMOTE=1 — operators must explicitly
    acknowledge the exposure. Bare `:port` (= 0.0.0.0) is NOT loopback.

Also picks up luxfi/threshold v1.6.10 which removes the pulsar/corona
in-memory-token surface (Red HIGH B2) — pulsar.*/corona.* now return a
typed "not yet implemented" error.

Tests:
  - TestIsThresholdLoopback locks the loopback predicate (8 cases:
    127.0.0.1, [::1], localhost OK; 0.0.0.0, :port, RFC1918 refused).
  - TestEmbeddedDispatcher_AuthGate proves 401 on missing/wrong token,
    200 on valid token through the dispatcher mpcd installs.

4 tests pass in cmd/mpcd (was 2, +2).
2026-05-13 16:46:22 -07:00
..

mpcd — MPC Daemon

mpcd is a threshold-signature daemon. It speaks ZAP wire protocol over TLS 1.3 directly between peers — no NATS, no Consul, no PostgreSQL in the critical path. The daemon is the same binary in every deployment shape; the operating mode is selected entirely by flags.

Operating modes

There is exactly one start command (mpcd start) and the topology is determined by the --peer and --threshold flags:

Topology Flags Use
Standalone (1-of-1 self-loop) --threshold 1 (no --peer) Smoke tests, dev sandbox
Networked t-of-n cluster --threshold t --peer ... (n−1 peers) Production

In all cases mpcd runs the consensus-embedded transport (pkg/transport). The "consensus" here is local PoA membership over the node's own Ed25519 identities — it is intentionally not the Lux mainnet chain, because key shares must never leave the cluster.

Standalone (1-of-1)

mpcd start \
    --node-id solo \
    --listen :9999 \
    --api :9800 \
    --data /var/lib/mpcd \
    --threshold 1

Starts mpcd with itself as the only quorum member. /healthz will report signing_quorum: true immediately. Keygen and signing of fresh wallets require ≥2 parties — CGGMP21 and FROST cannot produce shares with a single party — so this mode exists only for boot-up smoke tests, e2e harness fixtures, and verifying TLS / membership / API plumbing. For real signing, run a multi-node cluster.

Networked (t-of-n)

# Node 0
mpcd start --node-id node0 --listen :9999 --api :9800 \
    --data /data/node0 --threshold 2 \
    --peer [email protected]:9999 --peer [email protected]:9999

# Node 1
mpcd start --node-id node1 --listen :9999 --api :9800 \
    --data /data/node1 --threshold 2 \
    --peer [email protected]:9999 --peer [email protected]:9999

# Node 2 — same pattern

The daemon expects the cluster size n = len(peers) + 1 and rejects start when threshold > n. See pkg/transport/registry.go::HasSigningQuorum for the quorum arithmetic.

Smoke test

go test -tags=integration ./cmd/mpcd/... -count=1 -timeout 90s

standalone_test.go builds mpcd, runs it with --threshold 1 on free ports and a fresh data directory, polls /healthz until quorum holds, and asserts the response shape. It does not exercise keygen — see e2e/ for that.