Files
mpc/cmd/mpcd
zooqueen b6ab5f46c1 reveal: the ring can be asked, and every node answers
pkg/reveal could open a secret; nothing could ask it to. This is the
asking: an opcode the KMS client has always sent, a backend that fans the
request out and waits for a quorum, and every node answering from its own
share.

Every node serves the answering half. A ring where only some answer is a
ring with a smaller quorum than its threshold claims, and the shortfall
would show up as a timeout rather than as the misconfiguration it is.

A node that holds no share for the key publishes that fact. Silence and
refusal look identical to a caller counting answers and they are not: one
means wait longer, the other means never — so a failed opening says how
many answered and names the nodes that would not.

The wait ends at a quorum rather than at the deadline. This runs at boot,
where a process that already has what it needs must not sit for twenty
seconds proving it, and one that will never get it must say so rather
than hang.

Answers are counted per party under a lock. A redelivery or a duplicated
subscription must not fill two seats — Open refuses such a set anyway,
but the wait should be honest as well as the result.

The two halves take a bus, a store and a name rather than reaching into
the backend, so the whole path is exercised over a bus a test controls:
the real Ed25519 keygen, each share in a store of its own, a root that
travels the ring and comes back, and a quorum that never forms saying
exactly why.

The interface gained a method, so two test mocks did too. That is the
compiler naming every implementation, which is the reason to put it
there.
2026-08-20 13:44:05 -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.