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:
- Client generates a full key pair using standard derivation (BIP-32/44)
- Client splits the private key into two shares using the appropriate protocol
- Client share is encrypted with the PRF-derived encryption key and stored locally
- Server share is sent to the MPC Signer worker and stored in a Durable Object
- 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 twoSigning Protocol
When a transaction needs to be signed:
- User authenticates with WebAuthn passkey
- PRF extension decrypts the client share
- Client and server execute a multi-round signing protocol
- The combined output is a standard signature (indistinguishable from single-party)
- 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:
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 lookupConfiguration
Enabling MPC
- Deploy the MPC Signer worker (
stratos-mpc-signer) - Add service binding in
wrangler.toml:
[[services]]
binding = "MPC_SIGNER"
service = "stratos-mpc-signer"- Set environment variables:
[vars]
MPC_ENABLED = "true"
MPC_AUTH_SECRET = "your-shared-secret"- 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:
// 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 signaturesAll chain signers (EVM, Solana, TON, TRON, Bitcoin) use the sign adapter, making MPC a zero-change upgrade for application developers.
Next Steps
- Security Architecture - Full security model
- Deployment Model - Infrastructure overview
- Developer Introduction - Build on Stratos Vault
