How it works
Cluster topology, ceremony coordination, the gossip + identity layer, and what the agent does (and doesn't) on every external request.
How it works
This page covers the agent's runtime model from an operator's perspective — what happens on every external request, how nodes coordinate intra-cluster, where the cryptography lives, and what the agent never does.
Cluster topology
A cluster is a set of nodes that:
- Share one license certificate (the cert pins the licensee and the allowed curves).
- Share one intra-cluster transport (a message broker the nodes use to exchange ceremony envelopes — the details are operator-configured and invisible to clients).
- Trust each other's Ed25519 identity public keys (pinned per node under
identity.trusted_peers).
Each node has its own:
- Ed25519 signing key (cluster identity).
- X25519 keypair derived from the Ed25519 seed (envelope sealing).
- Storage volume (shares + ceremony state).
- HTTPS interface on its own URL.
The recommended deployment is 3 nodes per cluster: enough to run a 2-of-3 cohort with one party down, small enough that intra-cluster coordination stays cheap. Smaller and larger clusters work too — a 2-node / 2-of-2 cluster is a valid minimal setup, and larger clusters (5, 7, 9 nodes) are sometimes used for higher-threshold setups. Whatever the size, every node participates in each ceremony (maxShares equals the node count) and the operational footprint grows linearly per added node.
The cluster as a whole is one logical "MPC service" from a client's perspective — EmbeddedAgent typically points at all the nodes via the agent array, and the cluster's intra-cluster transport is invisible.
A ceremony, end-to-end
Take a 2-of-3 sign as the canonical example. The device, plus two of three cluster nodes, run through:
┌────────┐ HTTPS POST ┌──────────┐
│ Device │ ────────────────▶ │ node-1 │ ◀── chosen entry point
│ (party │ { keyShareId, … } │ (party 1)│
│ 0) │ └─────┬────┘
└────────┘ │ intra-cluster envelope
▼
┌──────────────────────┐
│ intra-cluster bus │
└──┬─────────────────┬─┘
▼ ▼
┌──────────┐ ┌──────────┐
│ node-2 │ │ node-3 │
│ (party 2)│ │ (party 3)│
└──────────┘ └──────────┘
Step 1 — Device hits the chosen node's HTTPS interface. The client (EmbeddedAgent or ClusterAgent) authenticates with its API key and posts the ceremony-create request. The receiving node validates the request (signature, peer list, allowed curve), creates a session, and immediately returns a session id + the next sequence number.
Step 2 — The receiving node fans out to the cluster. It publishes ceremony envelopes to the cluster's intra-cluster transport. Other cluster nodes that are in this ceremony's peer list (set at session-create time) pick up the envelopes and start their per-party state machines.
Step 3 — Each participating node runs its piece of the protocol. Generates its commitment, seals its share for each peer (X25519-encrypted envelope), publishes outbound. Inbound from the peers gets processed by the same state machine until the per-node phase reaches Complete.
Step 4 — Device long-polls for outbound. While the cluster's internal ceremony runs, the device polls the agent for envelopes addressed to it. Each envelope is sealed for the device's X25519 pubkey; the device opens locally, processes, and POSTs its own outbound back.
Step 5 — Each node converges on Complete independently. Once a node has collected all the envelopes it needs from every peer (per the protocol's phase requirements), its state machine advances to Complete and emits the result (partial signature for sign, presignature for presigning, key share for DKG, fresh key share for reshare).
Step 6 — The SDK finalises locally. Agents never assemble a finalised (r, s) signature. Each node produces only its partial signature; the SDK on the device collects all partials and combines them locally via Scheme.finalizeSignature. This is the only way to keep the cluster symmetric — any node can be the "first contact" for any ceremony.
What the agent NEVER does
The cluster intentionally keeps a narrow surface to make audit, threat-model, and incident-response straightforward.
- No private-key reconstruction. Each node only ever sees its own share + cryptographically-sealed envelopes from peers. The full private key never exists anywhere — every party's share is one Lagrange-evaluation of a polynomial that no one can reconstruct without
threshold-many shares (and even then, the reconstruction happens off-line during cold-recovery, never inside the cluster). - No signature assembly. Each node's
Completeresult for a sign ceremony is a PARTIAL signature. The cluster deliversthreshold-many partials to the client; the client combines them locally viaScheme.finalizeSignature. There is no signature-finalization path on any node. - No leader-driven coordination. Every node is symmetric. The client picks a "first contact" by URL but the ceremony itself has no designated leader — every participating node runs the same state machine, exchanges envelopes peer-to-peer, and converges on Complete independently.
- No cluster-side recovery. Cold-recovery (reconstructing the root private key off-line from sealed shards) is OFF-cluster by design. The agent's
exportRecoveryBundleproduces a SHARD; combining shards happens in the integrator's process viaRecoveryBundle.recover, never inside the cluster.
These rules are foundational to the security model.
Session lifecycle
Each ceremony creates one session per participating node. The session tracks:
session_id(UUID, shared across nodes for the same ceremony).key_id(the wallet this ceremony is for).kind(Dkg/Presigning/Sign/Reshare).phase(Initialising/AwaitingPeerCommitments/AwaitingPeerShares/Reconstructing/Complete/Failed/Expired).created_at,last_updated_at,expires_at.- The full envelope trail in/out for the per-node state machine.
Sessions live until either:
- The retention sweeper hard-deletes them (
security.session_retention_msafter the last update, by default 24h after Complete/Failed). - The session times out (
security.session_timeout_ms, default 5 min from creation; expired rows are markedExpiredand the GC sweep clears them on the same cadence).
For audit / forensics, raise session_retention_ms and list sessions by key_id. Each session holds the full envelope trail in/out, so a post-hoc analysis can reconstruct exactly which messages flowed during a ceremony.
Identity and trust
Each node has one Ed25519 keypair. The private hex sits in identity.private_key_hex (read from env in practice). The public key:
- Signs every outbound envelope (and is verified by the receiving node).
- Is announced in
/node/infoso clients can pin it client-side. - Is the basis for X25519 envelope-sealing (derived deterministically from the Ed25519 seed).
identity.allow_dynamic_nodes: false + trusted_peers: [<hex>, ...] is the production posture. New nodes joining the cluster require a config update (pin their pub key everywhere) before they can participate in ceremonies. allow_dynamic_nodes: true skips the pin check; useful for dev, dangerous in production (any node that presents a valid Ed25519 signature can join).
When you pin keys, each node's trusted_peers must list every node in the cluster — including the node itself. Each node processes its own ceremony messages alongside its peers', so a list that omits the node's own public key makes it reject its own messages and the ceremony stalls. List all node keys (this one included), or leave trusted_peers: [] empty to disable pinning entirely.
There's no automatic key rotation today — rotating a node's identity requires:
- Generate a new private hex for that node.
- Update every peer's
trusted_peerswith the new public key (rolling restart). - Update the rotating node's
private_key_hexand restart. - Run a reshare on every wallet the rotating node was a party to, so the rotated identity is now baked into the cohort metadata.
Step 4 is required because cohort metadata captured at DKG time pins each peer's Ed25519 pubkey; sign ceremonies verify against the captured value and reject the rotated key until reshare replaces it.
Next
- Deployment → — get a cluster running locally first.
- Configuration → — every config knob explained.
- Integration patterns → — connect the cluster to your devices or backend.
Configuration
Every MPC Agent config knob — identity, license, intra-cluster transport, storage, API auth, rate limits, policy webhooks, performance & limits.
Integration patterns
Connect your cluster to the right client surface. Embedded Agent for device + cloud agents, ClusterAgent client for server-side custody, hybrid for both.