Разработка веб-кошелька

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1 услугВсе 1306 услуг
Разработка веб-кошелька
Средняя
~1-2 недели
Часто задаваемые вопросы
Направления блокчейн-разработки
Этапы блокчейн-разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1258
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1170
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    873
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1092
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    563
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    830

Разработка веб-криптокошелька

Криптокошелёк — это не место хранения монет. Монеты хранятся on-chain, кошелёк хранит приватный ключ для их подписания. Это принципиальное отличие от банковского счёта, и оно определяет всю архитектуру: где и как хранить ключ, как подписывать транзакции, как защитить от компрометации.

Веб-кошелёк — наиболее доступный вариант (не нужно устанавливать расширение или приложение), но и наиболее атакуемый: browser environment, XSS угрозы, supply chain атаки на JavaScript зависимости. MetaMask, Coinbase Wallet, Rainbow — браузерные расширения, а не чистый web. Чистый web-кошелёк требует дополнительных мер безопасности.

Типы кошельков и архитектурный выбор

EOA vs Smart Account

EOA (Externally Owned Account) — классический кошелёк. Один приватный ключ → один адрес. Просто, совместимо со всем. Проблема: потеря ключа = потеря доступа навсегда. Нет возможности social recovery, multi-factor auth, spending limits.

Smart Account (ERC-4337 Account Abstraction) — смарт-контракт кошелёк. Логика валидации транзакций программируема. Возможности: social recovery (восстановление через trusted contacts), session keys (ограниченные ключи для dApp без полного доступа), batched transactions (несколько операций в одном вызове), gas sponsorship (paymaster платит gas за пользователя), signature verification любым алгоритмом (не только ECDSA secp256k1).

Для потребительского web-кошелька в 2024-2025 году — рекомендую ERC-4337. Учите пользователей не seed phrases, а recovery методам понятным обычному человеку.

Хранение ключей в браузере

Ключевой вопрос безопасности. Варианты от наименее к наиболее безопасному:

localStorage / sessionStorage — никогда не использовать для приватных ключей. Доступен любому JavaScript на странице, любому XSS.

IndexedDB с шифрованием — приватный ключ шифруется через Web Crypto API (AES-GCM 256-bit) ключом производным от пароля пользователя (PBKDF2 или Argon2 с высоким cost factor). Зашифрованный blob хранится в IndexedDB. Это стандарт для браузерных кошельков.

async function encryptPrivateKey(
  privateKey: Uint8Array,
  password: string
): Promise<{ encrypted: ArrayBuffer; salt: Uint8Array; iv: Uint8Array }> {
  const salt = crypto.getRandomValues(new Uint8Array(32));
  const iv = crypto.getRandomValues(new Uint8Array(12)); // 96-bit для GCM
  
  // Derive encryption key from password
  const keyMaterial = await crypto.subtle.importKey(
    "raw",
    new TextEncoder().encode(password),
    "PBKDF2",
    false,
    ["deriveKey"]
  );
  
  const encryptionKey = await crypto.subtle.deriveKey(
    {
      name: "PBKDF2",
      salt,
      iterations: 600000, // OWASP рекомендует 600k для SHA-256
      hash: "SHA-256",
    },
    keyMaterial,
    { name: "AES-GCM", length: 256 },
    false,
    ["encrypt"]
  );
  
  const encrypted = await crypto.subtle.encrypt(
    { name: "AES-GCM", iv },
    encryptionKey,
    privateKey
  );
  
  return { encrypted, salt, iv };
}

WebAuthn + hardware key — наиболее безопасный вариант. Приватный ключ кошелька никогда не покидает WebAuthn authenticator (платформенный ключ в TPM/Secure Enclave, или hardware key типа YubiKey). Операция подписи происходит внутри устройства.

Реализация через Passkey: пользователь создаёт passkey (WebAuthn credential), приватный ключ генерируется в Secure Enclave iPhone/Android/Windows TPM. Для ERC-4337 кошелька — смарт-контракт может верифицировать P-256 (secp256r1) подписи WebAuthn. Daimo и Coinbase Smart Wallet используют именно этот подход.

MPC кошельки

Multi-Party Computation: приватный ключ никогда не существует целиком в одном месте. Он split на shares между несколькими сторонами (пользователь + сервер, или пользователь + устройство 1 + устройство 2). Подпись требует threshold участников, но ни один не знает полного ключа.

Провайдеры MPC-as-a-service: Privy, Dynamic, Web3Auth, Fireblocks Web3. Для non-custodial MPC — пользователь держит один share, сервис другой. Компрометация сервиса не означает компрометацию ключа.

Минус MPC: signing более медленное (multi-round protocol) и требует доступности обоих участников. Если сервис недоступен — нельзя подписать.

Key Derivation и HD кошельки

BIP-39 и BIP-44

Стандартный Web3 кошелёк использует seed phrase (мнемонику) — 12 или 24 слова из BIP-39 словаря. Из seed через PBKDF2 (2048 iterations) выводится master private key. Из master key через BIP-32 derivation выводятся дочерние ключи по пути.

BIP-44 определяет стандартный derivation path: m/purpose'/coin_type'/account'/change/address_index.

Для Ethereum: m/44'/60'/0'/0/0 — первый аккаунт. m/44'/60'/0'/0/1 — второй. Это позволяет одной seed phrase управлять бесконечным числом адресов, детерминированно. Импорт seed в MetaMask, Rainbow, Ledger — получишь те же адреса.

import { mnemonicToSeed } from "@scure/bip39";
import { HDKey } from "@scure/bip32";

async function deriveAccount(mnemonic: string, index: number) {
  const seed = await mnemonicToSeed(mnemonic);
  const masterKey = HDKey.fromMasterSeed(seed);
  
  const derivationPath = `m/44'/60'/0'/0/${index}`;
  const childKey = masterKey.derive(derivationPath);
  
  return {
    privateKey: childKey.privateKey!,
    address: computeAddress(childKey.publicKey!),
  };
}

Библиотеки: @scure/bip39 и @scure/bip32 — modern, audited, tree-shakeable замены noble-secp256k1 и bip39.

Архитектура веб-кошелька

Separation of concerns: UI vs Signing

Критическое правило: код, который имеет доступ к приватному ключу, должен быть изолирован от кода, который взаимодействует с dApps и интернетом. XSS в UI layer не должен компрометировать ключ.

Реализация через Web Worker или Service Worker: signing операции происходят в изолированном worker, UI слой не имеет прямого доступа к ключу. Worker получает запрос на подпись, показывает пользователю что подписывается, получает подтверждение, возвращает подпись (не ключ).

// Main thread — UI
async function signTransaction(txRequest: TransactionRequest): Promise<string> {
  return new Promise((resolve, reject) => {
    const worker = new Worker("/signing-worker.js");
    
    worker.postMessage({ type: "SIGN_TX", payload: txRequest });
    
    worker.onmessage = (e) => {
      if (e.data.type === "SIGNED") resolve(e.data.signature);
      if (e.data.type === "REJECTED") reject(new Error("User rejected"));
      if (e.data.type === "ERROR") reject(new Error(e.data.error));
    };
  });
}

// signing-worker.js — изолированный worker с доступом к ключу
self.onmessage = async (e) => {
  if (e.data.type === "SIGN_TX") {
    const approved = await requestUserApproval(e.data.payload);
    if (!approved) {
      self.postMessage({ type: "REJECTED" });
      return;
    }
    const signature = await signWithStoredKey(e.data.payload);
    self.postMessage({ type: "SIGNED", signature });
  }
};

WalletConnect и dApp интеграция

WalletConnect v2 — стандарт для связи кошелька с dApp. Работает через relay server: dApp создаёт pairing QR-код или deep link, кошелёк сканирует, устанавливается encrypted session. Все запросы (eth_signTypedData, eth_sendTransaction) идут через этот канал.

Для веб-кошелька: @walletconnect/web3wallet SDK. Кошелёк выступает как "wallet" (не "dapp") в WalletConnect терминологии.

import { Web3Wallet } from "@walletconnect/web3wallet";
import { Core } from "@walletconnect/core";

const core = new Core({ projectId: WALLETCONNECT_PROJECT_ID });
const web3wallet = await Web3Wallet.init({
  core,
  metadata: {
    name: "My Wallet",
    description: "Custom Web3 Wallet",
    url: "https://mywallet.app",
    icons: ["https://mywallet.app/icon.png"],
  },
});

// Обработка incoming requests от dApps
web3wallet.on("session_request", async (event) => {
  const { id, topic, params } = event;
  const { request } = params;
  
  if (request.method === "eth_sendTransaction") {
    const approved = await showTransactionConfirmation(request.params[0]);
    if (approved) {
      const txHash = await sendTransaction(request.params[0]);
      await web3wallet.respondSessionRequest({
        topic,
        response: { id, result: txHash, jsonrpc: "2.0" },
      });
    } else {
      await web3wallet.respondSessionRequest({
        topic,
        response: { id, error: { code: 4001, message: "User rejected" }, jsonrpc: "2.0" },
      });
    }
  }
});

EIP-1193 Provider

Для прямой интеграции с dApps через window.ethereum: кошелёк инжектирует EIP-1193 совместимый provider в DOM. Это то, что делает MetaMask. Для web-кошелька в tab (не расширения) — это ограничено тем же origin, что не идеально.

Альтернатива: Service Worker как background process (Progressive Web App), который может инжектировать provider и обрабатывать запросы от других tabs того же origin.

ERC-4337 Account Abstraction реализация

UserOperation flow

Вместо обычной Ethereum транзакции, ERC-4337 вводит UserOperation — структуру данных, которую подписывает кошелёк и отправляет в Bundler (не в mempool напрямую):

interface UserOperation {
  sender: string;          // адрес smart account
  nonce: bigint;
  initCode: Hex;           // для создания нового аккаунта
  callData: Hex;           // что аккаунт должен выполнить
  callGasLimit: bigint;
  verificationGasLimit: bigint;
  preVerificationGas: bigint;
  maxFeePerGas: bigint;
  maxPriorityFeePerGas: bigint;
  paymasterAndData: Hex;   // paymaster для спонсирования газа
  signature: Hex;          // подпись владельца
}

Bundler собирает UserOperations, упаковывает в батч, отправляет через EntryPoint контракт. EntryPoint валидирует подписи и исполняет операции.

Кошелёк взаимодействует с Bundler через JSON-RPC API (ERC-4337 специфичные методы: eth_sendUserOperation, eth_getUserOperationByHash). Провайдеры bundler: Pimlico, Alchemy, Biconomy.

Session Keys

Session key — ограниченный ключ без полного доступа к аккаунту. Пользователь создаёт session key для конкретного dApp на конкретный период. dApp использует этот ключ для подписания операций — пользователю не нужно подтверждать каждую транзакцию.

Для GameFi: пользователь создаёт session key на 8 часов игровой сессии. Key может только вызывать game-specific контракты, не может переводить ETH или ERC20. После 8 часов — истекает автоматически.

Реализация: через SmartAccount validator module (ZeroDev Kernel, Safe modules). Session key policy хранится в смарт-контракте.

Безопасность

Угрозы и митигация

XSS атаки. Content Security Policy (строгая: script-src 'self' 'wasm-unsafe-eval'), Subresource Integrity для внешних скриптов, избегать innerHTML, использовать React (автоматический escaping). Регулярный npm audit и Snyk для supply chain анализа.

Clipboard атаки. Пользователь копирует адрес, malware подменяет адрес в clipboard. Решение: показывать первые и последние N символов адреса, просить визуально проверить. Некоторые кошельки показывают адрес как идентicon (уникальный аватар) — быстрее заметить подмену.

Phishing. Поддельный сайт кошелька. Решение: PWA с verified domain, browser-level warnings, ENS для верификации.

Seed phrase exposure. Показ seed phrase в UI — максимальный риск. Никогда не передавать seed через сеть, показывать только при создании, требовать явного user action для просмотра, добавить "are you alone?" предупреждение.

Private key в памяти. После расшифровки ключ живёт в JavaScript heap. Garbage collector не гарантирует немедленное очищение. Митигация: Uint8Array.fill(0) после использования, хранить ключ в Worker где GC более предсказуем.

Аудит зависимостей

Кошелёк использует много crypto-библиотек. Критичные для проверки:

  • @noble/secp256k1 или @noble/curves — подпись транзакций
  • @scure/bip32, @scure/bip39 — HD derivation
  • ethers или viem — взаимодействие с chain

Все @noble/* и @scure/* библиотеки от Paulmillr — аудированы, minimalist, без зависимостей. Предпочитать их более "корпоративным" альтернативам.

Мультичейн поддержка

Ethereum-совместимые chain (Polygon, Arbitrum, Base, Optimism) — один адрес, разные RPC. Достаточно поддерживать EIP-155 chain ID и переключение RPC.

Bitcoin или Solana — другой алгоритм подписи (secp256k1 с разным форматом / ed25519), другой derivation path. Требует отдельной ключевой логики.

Для мультичейн кошелька: абстракция chain-specific логики за общим интерфейсом. IChainSigner с методом signTransaction(tx) — каждый chain имеет свою реализацию.

Стек и сроки

Компонент Технология Срок
Key management Web Crypto API + @scure 3-4 недели
HD wallet (BIP-39/44) @scure/bip32 + bip39 1-2 недели
Transaction signing viem + ethers 2-3 недели
ERC-4337 integration permissionless.js + Pimlico 3-4 недели
WalletConnect v2 @walletconnect/web3wallet 2-3 недели
UI (React + TypeScript) React + Tailwind 5-7 недель
Security hardening CSP + Web Worker isolation 2-3 недели
Multi-chain Chain abstraction layer 3-4 недели
Testing + audit prep Vitest + Playwright 3-4 недели

MVP веб-кошелёк (EOA, Ethereum, базовый UI): 8-10 недель. Production-ready с ERC-4337, мультичейн, WalletConnect, WebAuthn: 6-9 месяцев.

Обязателен security audit перед production запуском: кошелёк хранит ключи пользователей, одна уязвимость = потеря средств всей базы пользователей. Рекомендую аудит + bug bounty программу (Immunefi) с момента запуска.