Cosigner Architecture

What runs where, how the parts talk, and how a session is coordinated.

Cosigner architecture overview
Click to open the full-size diagram

Components

Cosigner runtime

One Go binary, shipped as ghcr.io/fystack/cosigner for linux/amd64 and linux/arm64. It joins sessions; it does not coordinate them.

  • MPC engine: Fystack's fork of tss-lib, same as MPCium. ECDSA uses GG18 on secp256k1. EdDSA uses tss-lib threshold Ed25519.
  • Identity key: Ed25519, created at pairing. Authenticates the cosigner and signs its messages.
  • Key store: AES-256-GCM encrypted rows in SQLite (default), Badger or DynamoDB.
  • Gateway relay: One outbound gRPC stream. Sends presence every 5 seconds.
  • Approval client: Calls your approval callback before every session.
  • Audit log: Signed, hash-chained JSONL.

Your services

  • cosigner-cli: Installs, verifies, pairs, starts and backs up the runtime.
  • Approval callback: Your HTTPS server. Returns approve or reject.
  • Storage key source: A password (manual profile) or AWS KMS (unattended profile).

Fystack services

  • Apex: Issues pairing tokens, registers cosigner keys, handles revocation, submits MPC requests.
  • Gateway: Accepts cosigner streams, checks identity and revocation, bridges traffic to NATS. One live stream per cosigner across all replicas.
  • Orchestrator: Runs the session state machine and signs every control message. Never touches key shares.
  • MPCium: Fystack's MPC nodes. See MPCium.

Channels

From → to Transport Auth Carries
Cosigner → Apex HTTPS One-time pairing token Identity registration
Cosigner ↔ Gateway gRPC stream, TLS EdDSA JWT, 60s lifetime All session traffic
Gateway ↔ Orchestrator ↔ MPCium NATS Ed25519 per message Coordination inside Fystack
Participant ↔ participant Relayed X25519 + ChaCha20-Poly1305 MPC rounds, end-to-end encrypted
Cosigner → Callback HTTPS RS256 JWT both ways Approve or reject

Stream envelopes

Every frame carries the authenticated workspace_id, cosigner_id and participant_id. The Gateway drops frames that don't match the stream identity.

Kind Direction Subject
CONTROL Orchestrator → cosigner mpc.v1.peer.<participant_id>.control
SESSION_EVENT Cosigner → orchestrator mpc.v1.session.<session_id>.event
P2P Participant ↔ participant mpc.v1.peer.<to>.session.<session_id>.p2p
PRESENCE Cosigner → orchestrator mpc.v1.peer.<participant_id>.presence

Trust boundaries

  • Gateway: Relays ciphertext. Can drop or delay traffic, cannot read or forge MPC messages.
  • Orchestrator: Signs every control message with a key the cosigner pinned at pairing. Unsigned or mis-signed messages are dropped.
  • Apex: Cannot skip your callback. Every session goes to your callback first.
  • Your cosigner: Cannot sign alone. The committee is fixed at keygen.

Details in Security Model.

Sessions

Each keygen, sign or reshare is one orchestrator session with a 120 second TTL.

Session states: created, waiting, key_exchange, active_mpc, completed, with failed and expired
Click to open the full-size diagram
  1. Admission: Orchestrator verifies the Apex signature, checks the committee and confirms every participant is online. One session per wallet and protocol at a time.
  2. waiting: Participants receive a signed session_start, run local checks (the cosigner calls your callback) and reply peer_joined, peer_ready.
  3. key_exchange: Participants exchange signed X25519 keys. Wallets with an approver also need its signed approval here.
  4. active_mpc: Orchestrator sends mpc_begin. Participants run tss-lib rounds directly.
  5. completed: All participants report the same result hash. Orchestrator signs and publishes the result.

A session fails on a participant error, a disconnect or mismatched results, and expires after 120 seconds. Either way the orchestrator broadcasts a signed session_abort.

Committees

Threshold t means t + 1 signers (tss-lib semantics). The committee of MPCium nodes and cosigners is fixed at keygen. Every later session must use the same party keys, identity keys and approver roles.

Connections

  • One live stream per cosigner identity. A second instance with the same identity is rejected.
  • Each session is bound to the stream that was live when it started. If that stream drops, the session fails with PARTICIPANT_DISCONNECTED.
  • A revoked cosigner gets PermissionDenied and stops.