Разработка MPC-кошелька
MPC (Multi-Party Computation) — криптографический протокол, при котором несколько сторон совместно вычисляют функцию над своими секретными данными, не раскрывая эти данные друг другу. Применительно к кошелькам: приватный ключ никогда не существует в полной форме ни на одном устройстве. Вместо этого каждая сторона держит «долю» (share) ключа, и подпись транзакции вычисляется совместно через MPC протокол. Украсть один share бесполезно — для подписи нужны все участники схемы.
Это принципиальное отличие от традиционного мультисига (M-of-N on-chain): мультисиг виден в блокчейне, требует N подписей on-chain (газ), и факт использования мультисига публичен. MPC-подпись выглядит как обычная одиночная подпись — ни блокчейн, ни наблюдатель не видит что за ней стоит совместное вычисление.
Fireblocks, ZenGo, Coinbase WaaS, Web3Auth — MPC-based продукты. Банковский и institutional сектор переходит на MPC именно из-за отсутствия single point of failure при хранении ключей.
Криптографическая основа
Threshold Signature Scheme (TSS)
Для Ethereum (secp256k1) наиболее распространён GG20 протокол (Genaro-Goldfeder 2020) или его улучшение CGGMP21. Схема t-of-n: любые t из n участников могут вычислить подпись, без t — невозможно.
Процесс состоит из двух фаз:
Keygen (distributed key generation): участники интерактивно генерируют shares, не раскрывая финальный ключ. По окончании каждый имеет share_i, а публичный ключ P = G * privateKey известен всем. Приватный ключ privateKey не существует нигде.
Signing: для подписи транзакции t участников запускают MPC протокол, каждый вводит свой share_i, на выходе — валидная ECDSA подпись. Ни один участник не узнал ключи других.
Классический кошелёк:
privateKey → address (публичный ключ)
подпись: sign(tx, privateKey)
MPC 2-of-3 кошелёк:
share_1 (сервер), share_2 (устройство), share_3 (backup)
address = publicKey (производный от shares, но ключ не существует)
подпись: MPC_sign(tx, share_1, share_2) ← любые 2 из 3
Реальные MPC библиотеки
Production-готовые реализации:
tss-lib (Binance): Go библиотека, реализует GG18/GG20. Используется в Binance Chain, Thorchain. Открытый исходный код.
multi-party-ecdsa (ZenGo): Rust, реализует GG20. Более актуальная, активная поддержка.
@sodot/sodot-node-sdk: коммерческое SDK для MPC-as-a-service. Быстрый старт, не нужно реализовывать протокол самостоятельно.
Silence Laboratories SDK: академически верифицированная реализация, используется в Web3Auth MPC.
Самостоятельная реализация MPC протокола — это месяцы работы криптографа и высокий риск ошибок. Для большинства проектов правильный выбор — готовая библиотека или MPC-as-a-service.
Архитектура 2-of-2 MPC кошелька
Типичная схема для consumer кошелька: одна доля на устройстве пользователя, одна на сервере. Транзакция требует участия обеих сторон.
Устройство (Share A) ←→ Сервер (Share B)
↑
Пользователь авторизует
Сервер не знает полного ключа. Устройство не знает полного ключа. Без сервера пользователь не может подписать (защита от кражи устройства). Без устройства сервер не может подписать (сервер не может украсть средства).
Keygen flow
// Упрощённая иллюстрация flow (не production-ready код)
import { MpcSigner } from '@sodot/sodot-node-sdk';
class MPCWallet {
private deviceSdk: MpcSigner; // на устройстве
private serverSdk: MpcSigner; // на сервере
async generateKey(): Promise<string> {
// 1. Инициируем keygen на обеих сторонах
const keygenId = crypto.randomUUID();
// 2. Устройство и сервер обмениваются MPC сообщениями через secure channel
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 }),
]);
// 3. Несколько раундов message exchange (GG20 требует 3-5 раундов)
await this.runKeygenRounds(keygenId, deviceShare, serverShare);
// 4. По окончании каждая сторона имеет свой share
// Публичный ключ известен обеим сторонам
const publicKey = await this.deviceSdk.getPublicKey(keygenId);
const address = ethers.computeAddress('0x' + publicKey);
// 5. Сохраняем shares
await this.deviceSdk.storeShare(keygenId, deviceShare); // в Secure Enclave
await this.serverSdk.storeShare(keygenId, serverShare); // в HSM сервера
return address;
}
async signTransaction(
address: string,
transaction: ethers.TransactionRequest
): Promise<string> {
// 1. Пользователь авторизует подпись (биометрия/PIN)
await this.authenticateUser();
// 2. Сериализуем транзакцию в байты для подписи
const txHash = ethers.keccak256(ethers.Transaction.from(transaction).unsignedSerialized);
const signingId = crypto.randomUUID();
// 3. MPC signing rounds
const signature = await this.runSigningProtocol(signingId, txHash, address);
// 4. Создаём подписанную транзакцию
const signedTx = ethers.Transaction.from({
...transaction,
signature,
});
return signedTx.serialized;
}
}
Share refresh (proactive security)
Классическая проблема: если сервер скомпрометирован год назад и share утёк, а мы узнали об этом сейчас — весь период мы были уязвимы. Share refresh решает это: периодически (раз в неделю/месяц) участники обновляют свои shares без изменения итогового ключа. Старые shares становятся невалидными. Украденный share год назад сегодня бесполезен.
async function refreshShares(walletId: string): Promise<void> {
// Оба участника запускают refresh протокол
// Новые shares математически связаны с тем же publicKey
// Но старые shares больше не работают
await Promise.all([
deviceSdk.refreshShare(walletId),
serverSdk.refreshShare(walletId),
]);
}
// Автоматический refresh по расписанию
setInterval(() => {
for (const walletId of activeWallets) {
refreshShares(walletId).catch(console.error);
}
}, 7 * 24 * 60 * 60 * 1000); // каждую неделю
2-of-3 с backup share
Схема 2-of-2 имеет слабое место: если сервер недоступен, пользователь не может подписать транзакции. Схема 2-of-3 решает это:
- Share 1: устройство пользователя
- Share 2: сервер (онлайн signing)
- Share 3: backup (зашифрован паролем пользователя, хранится в облаке или распечатан)
При нормальной работе: Device + Server = подпись. Если сервер недоступен: Device + Backup = подпись (аварийный recovery). Если устройство потеряно: Server + Backup = восстановление на новое устройство.
interface ShareDistribution {
deviceShare: EncryptedShare; // зашифрован biometric/PIN
serverShare: ServerShare; // в HSM сервера
backupShare: CloudEncryptedShare; // зашифрован паролем, в iCloud/Google Drive
}
async function recoverToNewDevice(
backupShare: CloudEncryptedShare,
backupPassword: string
): Promise<string> {
// 1. Расшифровываем backup share
const decryptedBackup = await decryptBackupShare(backupShare, backupPassword);
// 2. Запрашиваем у пользователя верификацию (email/SMS)
await verifyUserIdentity();
// 3. Запускаем key refresh с server + backup shares
// Создаём новый device share, делаем backup share невалидным
const newDeviceShare = await sdk.refreshWithParties([serverShare, decryptedBackup]);
// 4. Сохраняем новый device share на новом устройстве
await secureStore.save(newDeviceShare);
return 'Recovery complete';
}
Коммуникационный канал между сторонами
MPC протокол требует нескольких раундов обмена сообщениями между участниками. Для 2-of-2 (устройство + сервер):
WebSocket: минимальная латентность, нужен persistent connection. Оптимально для мобильных приложений.
REST polling: проще в реализации, выше латентность (100-500ms per round). Для 3-5 раундов GG20 — дополнительные 0.5-2.5 секунды.
Типичное время подписи через MPC: 0.5-2 секунды при хорошей сети. Для пользователя это ощутимо дольше чем локальная подпись (< 10ms). Нужно показывать прогресс в UI.
Серверная инфраструктура
HSM для серверных shares
Серверный share должен храниться в Hardware Security Module — физическом устройстве, из которого невозможно извлечь ключ через программный интерфейс. Варианты:
AWS CloudHSM: дорого ($1.4K/month per HSM), но enterprise-grade. Соответствие FIPS 140-2 Level 3.
AWS KMS (software): дешевле, не настоящий HSM, но достаточно для большинства кейсов.
HashiCorp Vault: self-hosted, поддерживает интеграцию с аппаратными HSM, auto-unseal через KMS.
Для стартапа: AWS KMS + encryption at rest достаточно на начальном этапе. HSM — при росте до institutional клиентов.
Аутентификация перед signing
Сервер должен убедиться что запрос на подпись инициирован авторизованным пользователем:
async function authorizeSigningRequest(
walletId: string,
txHash: string,
authToken: string
): Promise<boolean> {
// Верифицируем JWT (выданный при биометрической аутентификации на устройстве)
const payload = await verifyJWT(authToken);
if (payload.walletId !== walletId) return false;
if (payload.exp < Date.now() / 1000) return false;
// Опционально: политики транзакций
// - Не более $10K в сутки без дополнительной верификации
// - Whitelist адресов для крупных переводов
const tx = parseTransaction(txHash);
const policy = await getUserPolicy(walletId);
if (tx.valueUSD > policy.dailyLimit) {
// Требуем дополнительное подтверждение (email, SMS)
await requireAdditionalVerification(walletId, tx);
}
return true;
}
Преимущества vs классический мультисиг
| Критерий | MPC Wallet | On-chain Multisig |
|---|---|---|
| Видимость в блокчейне | Обычная подпись | N подписей публично |
| Газ за транзакцию | Обычный | +20-50% (дополнительные подписи) |
| Single point of failure | Отсутствует | Зависит от конфигурации |
| Recovery при утере устройства | Через backup share | Через другие подписанты |
| Совместимость | Любой EVM кошелёк | Требует multisig aware dApp |
| Сложность реализации | Высокая | Низкая (Gnosis Safe) |
MPC оправдан для: institutional custody, wallets-as-a-service, кошельков без seed phrase (mobile-first), встроенных кошельков в приложения.
Gnosis Safe (on-chain multisig) оправдан для: DAO treasury, team wallets, случаев где нужна максимальная прозрачность on-chain.
Стек и библиотеки
| Компонент | Технология |
|---|---|
| MPC протокол | tss-lib (Go) или 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 |
Процесс работы
Архитектурный дизайн (1-2 недели). Выбор MPC протокола (t-of-n схема), MPC-as-a-service vs self-hosted, share distribution схема, backup и recovery flows, серверная инфраструктура.
Keygen и signing реализация (3-4 недели). Интеграция MPC библиотеки, коммуникационный канал, share storage.
Мобильное/web приложение (2-3 недели). UX onboarding, transaction flow с MPC подписью, progress indicators, recovery UI.
Серверная часть (2-3 недели). Auth сервис, signing API, policy engine, HSM интеграция.
Security review (2 недели). MPC протокол security analysis, серверная инфраструктура pentest, share storage review.
Тестирование и launch (1-2 недели). End-to-end тесты всех сценариев (normal, device loss, server downtime), нагрузочное тестирование.
Полный цикл MPC кошелька: 4-6 месяцев. Это сложнее обычного кошелька из-за MPC протокола и необходимости криптографической экспертизы. Стоимость рассчитывается после детализации схемы и выбора MPC библиотеки.







