Skip to content

MPC Signing Architecture

Multi-Party Computation (MPC) threshold signing for enhanced key security.


Overview

MPC signing eliminates the single point of failure in traditional key management. Instead of one party holding the complete private key, the key is split into shares distributed between the client device and a server-side worker. Signatures are produced through a cryptographic protocol that never reconstructs the full key.

┌─────────────────────────────────────────────────────────────────┐
│                     Traditional vs MPC Signing                    │
│                                                                   │
│  Traditional:                                                     │
│  ┌────────────────────┐                                          │
│  │ Full Private Key   │ → sign(message) → Signature              │
│  │ (Single point of   │                                          │
│  │  failure)           │                                          │
│  └────────────────────┘                                          │
│                                                                   │
│  MPC (Stratos Vault):                                            │
│  ┌──────────────┐  ┌──────────────┐                              │
│  │ Client Share │  │ Server Share │                               │
│  │ (x1 / f(1)) │  │ (x2 / f(2)) │                               │
│  └──────┬───────┘  └──────┬───────┘                              │
│         │    Protocol      │                                      │
│         └──────┬───────────┘                                      │
│                ▼                                                   │
│          Signature                                                │
│    (Key never reconstructed)                                     │
└─────────────────────────────────────────────────────────────────┘

How It Works

Key Generation

During wallet registration with MPC enabled:

  1. Client generates a full key pair using standard derivation (BIP-32/44)
  2. Client splits the private key into two shares using the appropriate protocol
  3. Client share is encrypted with the PRF-derived encryption key and stored locally
  4. Server share is sent to the MPC Signer worker and stored in a Durable Object
  5. The full key is zeroed from memory and never persisted
Registration Flow:

User → WebAuthn PRF → Derive Key → Split into Shares

                          ┌─────────────┴─────────────┐
                          │                           │
                   Client Share              Server Share
                   PRF-encrypted             Sent to DO
                   Stored in D1              Stored in DO
                          │                           │
                     key_type =                 mpc_key_id
                    'mpc_share'               links the two

Signing Protocol

When a transaction needs to be signed:

  1. User authenticates with WebAuthn passkey
  2. PRF extension decrypts the client share
  3. Client and server execute a multi-round signing protocol
  4. The combined output is a standard signature (indistinguishable from single-party)
  5. All nonces and session data are zeroed immediately

Lindell 2PC-ECDSA (secp256k1)

Used for Ethereum, Bitcoin, TRON, and other secp256k1-based chains.

Key Splitting

The private key x is split multiplicatively:

Full key:     x
Client share: x1 (random)
Server share: x2 = x * x1^(-1) mod N

Verification: x1 * x2 = x (mod N)
Public key:   P = x * G (unchanged, standard public key)

Paillier Encryption

The protocol uses Paillier homomorphic encryption to enable computation on encrypted values:

  • Client generates a Paillier keypair during keygen
  • Paillier public key is stored with client data
  • Paillier secret key components (lambda, mu, n, n2) are stored server-side
  • Homomorphic properties allow the server to compute on encrypted shares

Signing Protocol (3 Rounds)

Client                               Server
  │                                     │
  │  Round 1: Nonce Exchange            │
  │  k1 = random()                      │
  │  R1 = k1 * G                        │
  │  ────── R1 ──────────────────────>  │
  │                                     │  k2 = random()
  │                                     │  R2 = k2 * G
  │  <─────── R2, sessionId ──────────  │
  │                                     │
  │  Round 2: Partial Signature         │
  │  R = k1 * R2 (combined nonce)      │
  │  r = R.x mod n                     │
  │  c3 = Enc(pk, k1^(-1)*m + ...)    │
  │  ────── c3 ──────────────────────>  │
  │                                     │
  │  Round 3: Final Signature           │
  │                                     │  Decrypt c3
  │                                     │  Compute s = k2^(-1) * ...
  │  <─────── (r, s, v) ─────────────  │
  │                                     │
  │  Valid ECDSA signature              │

FROST 2-of-2 (ed25519)

Used for Solana, TON, and other ed25519-based chains. Implements RFC 9591.

Key Splitting (Feldman VSS)

The private key is shared using a polynomial:

Polynomial: f(x) = a0 + a1 * x (degree 1 for 2-of-2)
  where a0 = secret key

Client share: f(1) = a0 + a1
Server share: f(2) = a0 + 2*a1

Commitments: C0 = a0 * G, C1 = a1 * G (for verification)
Public key:  C0 (= a0 * G, standard ed25519 public key)

Signing Protocol (2 Rounds)

Client                               Server
  │                                     │
  │  Round 1: Nonce Commitments         │
  │  d1, e1 = random nonces            │
  │  D1 = d1*G, E1 = e1*G             │
  │  ────── D1, E1 ─────────────────>  │
  │                                     │  d2, e2 = random nonces
  │                                     │  D2 = d2*G, E2 = e2*G
  │  <─────── D2, E2, sessionId ─────  │
  │                                     │
  │  Round 2: Partial Signatures        │
  │  rho = H(message, D1+D2, E1+E2)   │
  │  R = (D1+D2) + rho*(E1+E2)        │
  │  c = H(R, PublicKey, message)      │
  │  z1 = d1 + rho*e1 + c*L1*f(1)    │
  │  ────── z1, message ─────────────>  │
  │                                     │  z2 = d2 + rho*e2 + c*L2*f(2)
  │                                     │  z = z1 + z2
  │                                     │  signature = (R, z)
  │  <─────── signature ─────────────  │
  │                                     │
  │  Valid ed25519 signature            │

Lagrange coefficients L1 and L2 ensure correct threshold reconstruction.


Server Architecture

MPC Signer Worker

The server side is a Cloudflare Worker with Durable Objects:

┌─────────────────────────────────────────────────────────────┐
│                    MPC Signer Worker                          │
│                                                               │
│  ┌─────────────────────────────────────────────────────────┐ │
│  │                  Router                                   │ │
│  │  POST /keygen      → Store server key share              │ │
│  │  POST /sign-init   → Round 1 (nonce exchange)            │ │
│  │  POST /sign-round2 → Round 2+ (produce signature)       │ │
│  │  POST /status      → Diagnostic (has key?)               │ │
│  │  POST /delete      → Remove all key data                 │ │
│  └──────────────────────────┬──────────────────────────────┘ │
│                              │                                │
│  ┌──────────────────────────▼──────────────────────────────┐ │
│  │              MpcKeyStore (Durable Object)                │ │
│  │                                                           │ │
│  │  storage:                                                 │ │
│  │    protocol: 'secp256k1' | 'ed25519'                     │ │
│  │    keyData:  JSON(server share + crypto params)           │ │
│  │                                                           │ │
│  │  in-memory:                                               │ │
│  │    sessions: Map<sessionId, SigningSession>               │ │
│  │    (auto-expire after 30s, one-time use)                 │ │
│  └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

Session Security

  • Each signing session gets a unique sessionId (UUID)
  • Sessions expire after 30 seconds
  • Sessions are one-time use - deleted immediately after signing
  • Nonces in expired sessions are explicitly zeroed
  • Worker-to-worker authentication via MPC_AUTH_SECRET

API Reference

Key Generation

POST /api/mpc/keygen
Authorization: Bearer <session_token>

{
  "protocol": "secp256k1" | "ed25519",
  "mpcKeyId": "unique-key-identifier",
  "serverKeyData": { ... }  // Protocol-specific server share
}

Sign Init (Round 1)

POST /api/mpc/sign-init
Authorization: Bearer <session_token>

// secp256k1:
{
  "protocol": "secp256k1",
  "mpcKeyId": "...",
  "clientR1Hex": "compressed-point-hex"
}

// ed25519:
{
  "protocol": "ed25519",
  "mpcKeyId": "...",
  "clientD1Hex": "point-hex",
  "clientE1Hex": "point-hex"
}

Sign Round 2

POST /api/mpc/sign-round2
Authorization: Bearer <session_token>

// secp256k1:
{
  "protocol": "secp256k1",
  "mpcKeyId": "...",
  "sessionId": "...",
  "ciphertext": "paillier-ciphertext-hex"
}

// ed25519:
{
  "protocol": "ed25519",
  "mpcKeyId": "...",
  "sessionId": "...",
  "clientZ1Hex": "partial-signature-hex",
  "messageHex": "message-hex"
}

Migration

POST /api/mpc/migrate
Authorization: Bearer <session_token>

{
  "chainType": "evm" | "svm" | "btc" | "tron" | "ton",
  "encryptedClientShare": "prf-encrypted-share",
  "mpcKeyId": "unique-key-identifier"
}

Status (Superadmin)

POST /api/mpc/status
Authorization: Bearer <superadmin_token>

Database Schema

MPC adds two columns to the wallet_addresses table:

sql
key_type TEXT DEFAULT 'full'  -- 'full' = PRF-encrypted full key
                              -- 'mpc_share' = PRF-encrypted client share

mpc_key_id TEXT               -- Durable Object name ID for server share lookup

Configuration

Enabling MPC

  1. Deploy the MPC Signer worker (stratos-mpc-signer)
  2. Add service binding in wrangler.toml:
toml
[[services]]
binding = "MPC_SIGNER"
service = "stratos-mpc-signer"
  1. Set environment variables:
toml
[vars]
MPC_ENABLED = "true"
MPC_AUTH_SECRET = "your-shared-secret"
  1. Toggle MPC in the admin panel under instance settings

Admin Panel

The admin panel provides:

  • MPC toggle - Enable/disable MPC for new registrations
  • Status monitoring - View MPC key status per user
  • Migration controls - Trigger migration for existing users

Transparent Integration

MPC signing is transparent to dock apps and SDK consumers. The SigningContext determines the mode:

typescript
// The sign adapter dispatches automatically
// Dock apps call the same SDK methods regardless of mode

// Local mode:
signSecp256k1(hash, key, { mode: 'local', privateKeyHex: '...' })

// MPC mode:
signSecp256k1(hash, key, { mode: 'mpc', mpcClient, clientKeyData: '...' })

// Both produce identical ECDSA/EdDSA signatures

All chain signers (EVM, Solana, TON, TRON, Bitcoin) use the sign adapter, making MPC a zero-change upgrade for application developers.


Next Steps

Enterprise-grade multi-chain wallet infrastructure.