Cosigner Security Model

No single party can move funds alone: not Fystack, not your host, not anything in between.

Who controls what

You Fystack
Cosigner host, storage, network Apex: pairing, identities, revocation
Storage key: password or KMS policy Orchestrator: sessions
Callback server, policy and keys Gateway: stream auth and relay
Backups, exports, upgrades MPCium nodes and workspace policies

Signing controls

Every signature passes all of these:

  1. Workspace policies and approval groups in Apex.
  2. Initiator signature: orchestrator accepts only Apex-signed requests.
  3. Committee pinning: participants and threshold must match keygen.
  4. Orchestrator signature: cosigner verifies it against the key pinned at pairing.
  5. Approver signature: required for wallets with an approver.
  6. Your callback: must return a signed approve.
  7. Threshold: your cosigner's share is required.

If Fystack were fully compromised, 1 to 5 could fail. Step 6 still holds: it runs on your infrastructure, with keys Fystack never sees, against the raw bytes being signed.

Authentication

Cosigner identity: Ed25519 key. Stored at state/cosigner.key (0600). Registered with Apex once via a pairing token. It signs the Gateway JWT, every session event and peer message, and audit entries.

Cosigner → Fystack: EdDSA JWT per stream.

Gateway JWT claims

{
  "iss": "<cosigner_id>",
  "sub": "<cosigner_id>",
  "aud": "cosigner-gateway",
  "ctx": "cosigner-gateway-auth-v1",
  "jti": "<random>",
  "iat": 1791794400,
  "exp": 1791794460
}

The Gateway accepts only EdDSA, checks audience and expiry (30s skew) and rejects reused jti for 2 minutes. Revocation disconnects live streams immediately.

Fystack → cosigner: The orchestrator key and fingerprint are pinned at pairing. Every control message must be signed by it with an increasing sequence number. Result signatures use a separate prefix so they can't be replayed as control messages.

Participant ↔ participant: Peer keys arrive in the signed session_start. Signed X25519 exchange, per-direction HKDF keys, ChaCha20-Poly1305. Gateway and orchestrator see only ciphertext.

Encryption at rest

Password or AWS KMS unwraps the storage KEK, HKDF derives four scope keys, each row is AES-256-GCM
Click to open the full-size diagram
  • Storage key: 32 bytes, unwrapped at start from a password (Argon2id t=3, 64 MiB) or AWS KMS. Memory only, zeroed after use.
  • Scope keys: HKDF-SHA256 derives separate keys for shares, preparams, checkpoints, metadata.
  • Rows: AES-256-GCM, random 12-byte nonce, bound to scope and row key. Moved rows fail to decrypt.
  • No plaintext shares leave: stripped from events. Exports are sealed to RSA-OAEP + AES-GCM or a passphrase.

Password unlock: cosigner-cli never puts the password in env or args. It opens a one-shot local socket; the container proves a 32-byte token via HMAC and receives the password AES-GCM encrypted.

Runtime hardening

  • Distroless, non-root image pinned by digest.
  • Read-only root filesystem; tmpfs for /tmp and /run/cosigner.
  • All capabilities dropped, no-new-privileges.
  • Config and secrets mounted read-only.
  • Limits: 128 KiB control messages, 20 starts and 10 aborts per minute, 5 concurrent sessions.

Audit trail

Append-only JSONL. Each entry is signed and includes the previous entry's hash, so edits and deletions are detectable. Ship it off-host.

Supply chain verification

Binaries, installers and images are built by GitHub Actions release workflows and signed with Sigstore. The certificate names the exact workflow and tag. The runtime pipeline also runs go mod verify, race tests, a reproducibility check, govulncheck and gosec, and attaches an SBOM and provenance.

cosigner-cli verifies on every up, update and doctor. To check by hand:

Verify release artifacts

cosign verify ghcr.io/fystack/cosigner@sha256:<digest> \
  --certificate-identity https://github.com/fystack/cosigner/.github/workflows/release.yml@refs/tags/<version> \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com

Digests and checksums: cosigner.fystack.io. Report mismatches to security@fystack.io.

Residual risks

  • Regular mode = host custody. Root or Docker access on the host can read memory. Nitro Enclave support (coming soon) will remove that.
  • Callback quality. A callback that approves without decoding the transaction adds nothing. Default deny.
  • Availability. A hostile host can block traffic or delete and roll back rows. Encryption detects edits, not deletion. Keep backups.
  • Duplicates. Never run the same cosigner on two hosts.
  • Recovery material. Backup passwords and export passphrases unlock your share. Store them separately.

Before real funds: test restore, restart, failure and IAM revocation, and get an independent review.