MPC Wallet Development
MPC (Multi-Party Computation) — cryptographic protocol where multiple parties jointly compute function over their secret data without revealing that data. For wallets: private key never fully exists anywhere. Instead each party holds "share" of key, and transaction signature computed jointly via MPC protocol. Stealing one share useless — need all participants to sign.
Fundamental difference from traditional multisig (M-of-N on-chain): multisig visible in blockchain, requires N signatures on-chain (gas), multisig usage public. MPC-signature looks like normal single signature — neither blockchain nor observer see shared computation behind it.
Fireblocks, ZenGo, Coinbase WaaS, Web3Auth — MPC-based products. Banking and institutional sector transitioning to MPC because no single point of failure in key storage.
Cryptographic Foundation
Threshold Signature Scheme (TSS)
For Ethereum (secp256k1), most common — GG20 protocol (Genaro-Goldfeder 2020) or improved CGGMP21. Scheme t-of-n: any t of n participants can compute signature, without t — impossible.
Process consists of two phases:
Keygen (distributed key generation): participants interactively generate shares without revealing final key. After completion each has share_i, public key P = G * privateKey known to all. Private key privateKey doesn't exist anywhere.
Signing: for transaction signature, t participants run MPC protocol, each inputs their share_i, output — valid ECDSA signature. No participant learns other's keys.
Classical wallet:
privateKey → address (public key)
sign: sign(tx, privateKey)
MPC 2-of-3 wallet:
share_1 (server), share_2 (device), share_3 (backup)
address = publicKey (derived from shares, but key doesn't exist)
sign: MPC_sign(tx, share_1, share_2) ← any 2 of 3
Real MPC Libraries
Production-ready implementations:
tss-lib (Binance): Go library, implements GG18/GG20. Used in Binance Chain, Thorchain. Open source.
multi-party-ecdsa (ZenGo): Rust, implements GG20. More recent, active support.
@sodot/sodot-node-sdk: commercial SDK for MPC-as-a-service. Quick start, no protocol implementation needed.
Silence Laboratories SDK: academically verified implementation, used in Web3Auth MPC.
Self-implementing MPC protocol — months of work by cryptographer, high error risk. For most projects — right choice is ready library or MPC-as-a-service.
2-of-2 MPC Wallet Architecture
Typical for consumer wallet: one share on user device, one on server. Transaction requires both parties.
Device (Share A) ←→ Server (Share B)
↑
User authorizes
Server doesn't know full key. Device doesn't know full key. Without server, user can't sign (protection from device theft). Without device, server can't sign (server can't steal funds).
Keygen Flow
// Simplified illustration (not production-ready code)
import { MpcSigner } from '@sodot/sodot-node-sdk';
class MPCWallet {
private deviceSdk: MpcSigner;
private serverSdk: MpcSigner;
async generateKey(): Promise<string> {
const keygenId = crypto.randomUUID();
const [deviceShare, serverShare] = await Promise.all([
this.deviceSdk.initKeygen(keygenId, { threshold: 2, parties: 2, partyIndex: 1 }),
this.serverSdk.initKeygen(keygenId, { threshold: 2, parties: 2, partyIndex: 2 }),
]);
// Several rounds of message exchange (GG20 requires 3-5 rounds)
await this.runKeygenRounds(keygenId, deviceShare, serverShare);
const publicKey = await this.deviceSdk.getPublicKey(keygenId);
const address = ethers.computeAddress('0x' + publicKey);
await this.deviceSdk.storeShare(keygenId, deviceShare);
await this.serverSdk.storeShare(keygenId, serverShare);
return address;
}
async signTransaction(
address: string,
transaction: ethers.TransactionRequest
): Promise<string> {
await this.authenticateUser();
const txHash = ethers.keccak256(ethers.Transaction.from(transaction).unsignedSerialized);
const signingId = crypto.randomUUID();
const signature = await this.runSigningProtocol(signingId, txHash, address);
const signedTx = ethers.Transaction.from({
...transaction,
signature,
});
return signedTx.serialized;
}
}
Share Refresh (Proactive Security)
Problem: if server compromised year ago and share leaked, we discover today — entire period vulnerable. Share refresh solves: periodically (weekly/monthly) participants update shares without changing final key. Old shares become invalid. Stolen share year ago useless today.
async function refreshShares(walletId: string): Promise<void> {
await Promise.all([
deviceSdk.refreshShare(walletId),
serverSdk.refreshShare(walletId),
]);
}
// Automatic refresh on schedule
setInterval(() => {
for (const walletId of activeWallets) {
refreshShares(walletId).catch(console.error);
}
}, 7 * 24 * 60 * 60 * 1000); // weekly
2-of-3 with Backup Share
2-of-2 weak point: if server unavailable, user can't sign. 2-of-3 solves:
- Share 1: user device
- Share 2: server (online signing)
- Share 3: backup (encrypted with user password, stored in cloud or printed)
Normal: Device + Server = signature. Server down: Device + Backup = signature (emergency recovery). Device lost: Server + Backup = recover on new device.
interface ShareDistribution {
deviceShare: EncryptedShare;
serverShare: ServerShare;
backupShare: CloudEncryptedShare;
}
async function recoverToNewDevice(
backupShare: CloudEncryptedShare,
backupPassword: string
): Promise<string> {
const decryptedBackup = await decryptBackupShare(backupShare, backupPassword);
await verifyUserIdentity();
const newDeviceShare = await sdk.refreshWithParties([serverShare, decryptedBackup]);
await secureStore.save(newDeviceShare);
return 'Recovery complete';
}
Communication Channel Between Parties
MPC protocol requires several message rounds. For 2-of-2 (device + server):
WebSocket: minimum latency, needs persistent connection. Optimal for mobile.
REST polling: simpler, higher latency (100-500ms per round). For 3-5 GG20 rounds — additional 0.5-2.5 seconds.
Typical MPC signing time: 0.5-2 seconds on good network. For user, noticeably longer than local signing (< 10ms). Show progress in UI.
Server Infrastructure
HSM for Server Shares
Server share stored in Hardware Security Module — physical device from which key can't be extracted via software. Options:
AWS CloudHSM: expensive ($1.4K/month per HSM), but enterprise-grade. FIPS 140-2 Level 3 compliance.
AWS KMS (software): cheaper, not real HSM, but sufficient for most.
HashiCorp Vault: self-hosted, supports hardware HSM integration, auto-unseal via KMS.
For startup: AWS KMS + encryption at rest sufficient initially. HSM for institutional clients.
Authentication Before Signing
Server must verify signing request initiated by authorized user:
async function authorizeSigningRequest(
walletId: string,
txHash: string,
authToken: string
): Promise<boolean> {
const payload = await verifyJWT(authToken);
if (payload.walletId !== walletId) return false;
if (payload.exp < Date.now() / 1000) return false;
const tx = parseTransaction(txHash);
const policy = await getUserPolicy(walletId);
if (tx.valueUSD > policy.dailyLimit) {
await requireAdditionalVerification(walletId, tx);
}
return true;
}
MPC vs On-chain Multisig
| Criterion | MPC Wallet | On-chain Multisig |
|---|---|---|
| Blockchain visibility | Regular signature | N signatures public |
| Gas for transaction | Normal | +20-50% (extra signatures) |
| Single point of failure | Absent | Depends on config |
| Recovery on device loss | Via backup share | Via other signers |
| Compatibility | Any EVM wallet | Requires multisig dApp |
| Implementation complexity | High | Low (Gnosis Safe) |
MPC justified for: institutional custody, wallets-as-a-service, keyless wallets (mobile-first), embedded wallets in apps.
Gnosis Safe (on-chain multisig) justified for: DAO treasury, team wallets, maximum on-chain transparency.
Stack and Libraries
| Component | Technology |
|---|---|
| MPC protocol | tss-lib (Go) or multi-party-ecdsa (Rust) |
| MPC-as-a-service | Sodot, Silence Labs, Web3Auth MPC Core |
| Mobile app | React Native + expo-secure-store |
| Backend | Node.js/Go + AWS KMS |
| HSM | AWS CloudHSM / HashiCorp Vault |
| Transport | WebSocket (signing rounds) + REST (management) |
| Blockchain | ethers.js v6 / viem |
Development Process
Architectural design (1-2 weeks). MPC protocol choice (t-of-n scheme), MPC-as-a-service vs self-hosted, share distribution, backup/recovery flows, server infrastructure.
Keygen and signing implementation (3-4 weeks). MPC library integration, communication channel, share storage.
Mobile/web application (2-3 weeks). UX onboarding, transaction flow with MPC signature, progress indicators, recovery UI.
Server implementation (2-3 weeks). Auth service, signing API, policy engine, HSM integration.
Security review (2 weeks). MPC protocol security analysis, server infrastructure pentest, share storage review.
Testing and launch (1-2 weeks). End-to-end all scenarios (normal, device loss, server downtime), load testing.
Complete MPC wallet: 4-6 months. More complex than regular wallet due to MPC protocol and cryptographic expertise needed. Cost calculated after schema details and MPC library choice.







