Розробка HD-гаманця (Hierarchical Deterministic)

Проєктуємо та розробляємо блокчейн-рішення повного циклу: від архітектури смарт-контрактів до запуску DeFi-протоколів, NFT-маркетплейсів та криптобірж. Аудит безпеки, токеноміка, інтеграція з наявною інфраструктурою.
Показано 1 з 1Усі 1306 послуг
Розробка HD-гаманця (Hierarchical Deterministic)
Середній
~1-2 тижні
Часті запитання

Напрямки блокчейн-розробки

Етапи блокчейн-розробки

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1306
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1218
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    920
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1147
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    610
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    885

Розробка HD Wallet

HD-гаманець (Hierarchical Deterministic wallet) дозволяє з однієї seed phrase генерувати нескінченне дерево ключевих пар. Стандарт BIP-32/BIP-39/BIP-44 — основа всіх сучасних гаманців: MetaMask, Ledger, Trust Wallet, Phantom. Розробка власного HD-гаманця вимагає точної реалізації стандартів, інакше користувачі не зможуть відновити кошти в інших гаманцях.

Стандарти та специфікації

BIP-39: генерація мнемонічної фрази (12/24 слова) з ентропії та конвертація в binary seed через PBKDF2.

BIP-32: деривація дерева ключів з master seed. Визначає child key derivation функцію (CKD) для normal та hardened ключів.

BIP-44: стандартна схема шляхів деривації: m / purpose' / coin_type' / account' / change / index. Наприклад, перша Ethereum адреса: m/44'/60'/0'/0/0.

EIP-55: checksum кодування Ethereum адрес (змішаний регістр).

Генерація ключів: BIP-39 та BIP-32

Мнемоніка та seed

import * as bip39 from "bip39";
import { HDKey } from "@scure/bip32";
import { keccak256 } from "ethereum-cryptography/keccak";
import { secp256k1 } from "ethereum-cryptography/secp256k1";

// Генерація 12-словної мнемоніки (128 бітів ентропії)
// Або 24 слова для 256 бітів — рекомендується для cold storage
function generateMnemonic(strength: 128 | 256 = 128): string {
  return bip39.generateMnemonic(strength);
}

// Конвертація мнемоніки в binary seed через PBKDF2
async function mnemonicToSeed(
  mnemonic: string,
  passphrase: string = ""
): Promise<Uint8Array> {
  if (!bip39.validateMnemonic(mnemonic)) {
    throw new Error("Invalid mnemonic");
  }
  // PBKDF2-HMAC-SHA512, 2048 ітерацій, 64 байти
  return bip39.mnemonicToSeed(mnemonic, passphrase);
}

Деривація дерева ключів

interface DerivedAccount {
  path: string;
  privateKey: Uint8Array;
  publicKey: Uint8Array;
  address: string;
  xpub: string;  // розширений публічний ключ для watch-only
}

function deriveAccount(
  seed: Uint8Array,
  accountIndex: number = 0,
  addressIndex: number = 0,
  coinType: number = 60  // 60 = Ethereum, 0 = Bitcoin, 501 = Solana
): DerivedAccount {
  const hdKey = HDKey.fromMasterSeed(seed);

  // BIP-44 шлях: m/44'/coinType'/account'/change/index
  // Апостроф = hardened деривація (захист: неможливо обчислити private key з public)
  const path = `m/44'/${coinType}'/${accountIndex}'/0/${addressIndex}`;

  const derived = hdKey.derive(path);

  if (!derived.privateKey) {
    throw new Error("Failed to derive private key");
  }

  const publicKey = secp256k1.getPublicKey(derived.privateKey, false);
  const address = publicKeyToAddress(publicKey);

  return {
    path,
    privateKey: derived.privateKey,
    publicKey,
    address,
    xpub: derived.publicExtendedKey
  };
}

function publicKeyToAddress(publicKey: Uint8Array): string {
  // Видаляємо prefix байт (0x04 для uncompressed)
  const pubKeyWithoutPrefix = publicKey.slice(1);
  const hash = keccak256(pubKeyWithoutPrefix);
  // Беремо останні 20 байтів
  const addressBytes = hash.slice(-20);
  return toChecksumAddress("0x" + Buffer.from(addressBytes).toString("hex"));
}

// EIP-55 checksum адреса
function toChecksumAddress(address: string): string {
  const addr = address.toLowerCase().replace("0x", "");
  const hash = Buffer.from(keccak256(Buffer.from(addr))).toString("hex");

  return "0x" + addr
    .split("")
    .map((char, i) => (parseInt(hash[i], 16) >= 8 ? char.toUpperCase() : char))
    .join("");
}

Безпечне зберігання ключів

Шифрування через Web Crypto API (Browser)

// Шифрування keystore по стандарту EIP-55 / Web3 Secret Storage
async function encryptKeystore(
  privateKey: Uint8Array,
  password: string
): Promise<EncryptedKeystore> {
  const salt = crypto.getRandomValues(new Uint8Array(32));
  const iv = crypto.getRandomValues(new Uint8Array(16));

  // Деривація ключа з пароля: PBKDF2, 600000 ітерацій (NIST рекомендація 2023)
  const passwordKey = await crypto.subtle.importKey(
    "raw",
    new TextEncoder().encode(password),
    "PBKDF2",
    false,
    ["deriveBits", "deriveKey"]
  );

  const encryptionKey = await crypto.subtle.deriveKey(
    {
      name: "PBKDF2",
      salt,
      iterations: 600_000,
      hash: "SHA-256"
    },
    passwordKey,
    { name: "AES-GCM", length: 256 },
    false,
    ["encrypt", "decrypt"]
  );

  const encrypted = await crypto.subtle.encrypt(
    { name: "AES-GCM", iv },
    encryptionKey,
    privateKey
  );

  return {
    version: 3,
    crypto: {
      ciphertext: Buffer.from(encrypted).toString("hex"),
      cipher: "aes-256-gcm",
      kdf: "pbkdf2",
      kdfparams: {
        dklen: 32,
        salt: Buffer.from(salt).toString("hex"),
        c: 600_000,
        prf: "hmac-sha256"
      },
      iv: Buffer.from(iv).toString("hex"),
      mac: ""
    }
  };
}

Мобільне зберігання

Для React Native — Secure Enclave (iOS) або Android Keystore:

import * as SecureStore from "expo-secure-store";
import * as Keychain from "react-native-keychain";

// Збереження мнемоніки в Secure Enclave / Android Keystore
// Ключі захищені біометрією пристрою
async function storeMnemonicSecurely(
  mnemonic: string,
  biometricPrompt: string
): Promise<void> {
  await Keychain.setGenericPassword(
    "hd_wallet_mnemonic",
    mnemonic,
    {
      accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
      accessControl: Keychain.ACCESS_CONTROL.BIOMETRY_CURRENT_SET,
      authenticationType: Keychain.AUTHENTICATION_TYPE.BIOMETRICS,
      securityLevel: Keychain.SECURITY_LEVEL.SECURE_HARDWARE
    }
  );
}

async function retrieveMnemonic(prompt: string): Promise<string> {
  const credentials = await Keychain.getGenericPassword({
    authenticationPrompt: { title: prompt }
  });
  if (!credentials) throw new Error("No stored mnemonic");
  return credentials.password;
}

Мультиаккаунт та watch-only режим

HD-гаманець підтримує кілька аккаунтів (різні BIP-44 account індекси) та watch-only режим через xpub:

class HDWalletManager {
  private hdKey: HDKey;

  constructor(seed: Uint8Array) {
    this.hdKey = HDKey.fromMasterSeed(seed);
  }

  // Отримати аккаунт за індексом
  getAccount(accountIndex: number): DerivedAccount {
    return deriveAccount(
      new Uint8Array(64), // placeholder
      accountIndex
    );
  }

  // xpub для watch-only гаманця — показує баланс без private key
  getAccountXpub(accountIndex: number): string {
    const accountKey = this.hdKey.derive(`m/44'/60'/${accountIndex}'`);
    return accountKey.publicExtendedKey;
  }

  // Генерація адрес з xpub без private key (для інтеграції hardware wallet)
  static deriveAddressFromXpub(
    xpub: string,
    addressIndex: number
  ): string {
    const hdKey = HDKey.fromExtendedKey(xpub);
    const derived = hdKey.derive(`m/0/${addressIndex}`);
    return publicKeyToAddress(derived.publicKey!);
  }
}

Watch-only режим дозволяє імпортувати тільки xpub — гаманець показує всі адреси та баланси, але не може підписувати транзакції. Використовується для моніторингу cold wallet.

Підпис транзакцій

import { Transaction, parseTransaction } from "viem";
import { privateKeyToAccount } from "viem/accounts";

async function signTransaction(
  privateKey: Uint8Array,
  txParams: {
    to: string;
    value: bigint;
    data: string;
    chainId: number;
    nonce: number;
    maxFeePerGas: bigint;
    maxPriorityFeePerGas: bigint;
    gas: bigint;
  }
): Promise<string> {
  const account = privateKeyToAccount(
    `0x${Buffer.from(privateKey).toString("hex")}`
  );

  const signedTx = await account.signTransaction({
    type: "eip1559",
    ...txParams
  });

  return signedTx;
}

Сумісність та тестування

Перед релізом, обов'язкова перевірка сумісності з іншими гаманцями:

Тест Перевірка
Імпорт в MetaMask Та ж мнемоніка → ті ж адреси
Імпорт в Ledger Live Через стандартний BIP-44 шлях
Імпорт в Trust Wallet 12/24 слова, перша адреса збігається
Test vectors BIP-39 Офіційні test-vectors з репозиторіїв

Офіційні test vectors для BIP-39 та BIP-32 знаходяться в trezor/python-mnemonic та bitcoin/bips репозиторіях. Прогон всіх test vectors — обов'язковий крок перед production deploy.