Иерархически детерминированный (HD) кошелёк — это дерево ключей, целиком выведенное из одного случайного числа. Стандарт BIP-32 описывает само дерево, BIP-44 — как в нём ориентироваться. Понимание этой конструкции отвечает на вопрос, который иначе выглядит мистикой: почему одна и та же сид-фраза, введённая в два разных кошелька, может показать разные адреса и нулевой баланс.
Зачем это понадобилось
Ранние биткоин-кошельки генерировали каждый ключ независимо. Такой кошелёк приходилось резервировать заново после создания каждого нового адреса — на каждый ключ около 32 байт плюс накладные расходы. Не успел сделать копию вовремя — потерял доступ к средствам, полученным на незарезервированные ключи.
Выход «использовать один-единственный адрес» технически безопасен, но резко снижает приватность — и вашу, и всех, с кем вы взаимодействуете. Те, кто ценил приватность, создавали новую пару ключей под каждую транзакцию и получали базы данных, которые разумно резервировать только на цифровых носителях.
Детерминированная генерация решает обе проблемы сразу. Хеш-функция всегда даёт один и тот же выход на одном и том же входе, но при малейшем изменении входа выход меняется непредсказуемо. Это позволяет взять одно случайное значение и превратить его в практически неограниченное число псевдослучайных значений — а позже повторить процесс и получить ровно те же ключи. Алисе с миллионом адресов достаточно сохранить одно число.
Как из сида вырастает дерево
Корневой сид (BIP-32 допускает от 128 до 512 бит и советует 256) подаётся на вход HMAC-SHA512 с ключом — строковой константой "Bitcoin seed". Полученные 512 бит режутся пополам и дают две вещи:
- мастер-ключ
m— приватный ключ вершины дерева (левая половина); - мастер-код цепочки (master chain code)
c— правая половина.
Мастер-ключи не генерируются напрямую именно потому, что расширенных пар ключей почти 2⁵¹², а сами ключи имеют длину 256 бит и дают примерно половину этого в терминах стойкости.
Из мастер-ключа обычным умножением на точку генератора получается мастер-публичный ключ M. Код цепочки при этом не декоративен: он вносит энтропию в функцию, порождающую дочерние ключи.
Функция деривации дочерних ключей
Дочерний ключ вычисляется из трёх составляющих:
- родительский приватный или публичный ключ;
- код цепочки (256 бит);
- индекс (32 бита).
Эти три значения объединяются и хешируются через HMAC-SHA512. Получившиеся 512 бит режутся пополам: правая половина становится кодом цепочки ребёнка, левая прибавляется к родительскому приватному ключу и даёт приватный ключ ребёнка.
Код цепочки нужен именно для того, чтобы знания индекса и одного дочернего ключа было недостаточно для вычисления остальных. Зная дочерний ключ, нельзя найти ни его родителя, ни его братьев и сестёр. Чтобы начать новую ветку и породить внуков, нужны и дочерний приватный ключ, и дочерний код цепочки.
Меняя индекс, получаем всю последовательность детей. У каждого ключа их 2³¹ — то есть 2 147 483 647. Это ровно половина диапазона 2³², и вторая половина зарезервирована под особый вид деривации, о котором ниже.
Сами по себе дочерние ключи неотличимы от случайных. Тот факт, что они часть последовательности, снаружи не виден.
Расширенные ключи
Ключ вместе с кодом цепочки называется расширенным ключом. Точнее было бы «расширяемый»: именно эта пара позволяет порождать детей.
Расширенных ключей два вида:
- xprv — приватный ключ плюс код цепочки. Порождает целую ветку: и приватные ключи, и публичные.
- xpub — публичный ключ плюс код цепочки. Порождает только публичные ключи.
Кодируются они в base58check со специальным номером версии, дающим узнаваемые префиксы xprv и xpub.
Деривация публичных ключей без приватных
Самое неочевидное свойство BIP-32 — возможность породить дочерние публичные ключи, ничего не зная о родительском приватном ключе.
Работает это на арифметике эллиптической кривой. Публичный ключ получается из приватного как K = k × G. Если прибавить одно и то же значение к обеим сторонам равенства, получится корректная новая пара:
K + (123 × G) == (k + 123) × G
Прибавление к публичному ключу выполняется полностью на открытых данных: значение G — глобальная константа. Прибавляемая величина называется твиком ключа (key tweak). Если твики выбираются детерминированным алгоритмом, то тот, кто не знает приватного ключа, может построить сколь угодно длинную последовательность публичных детей.
Практическое следствие — разделение кошелька на две части. Наблюдающий кошелёк с загруженным xpub генерирует адреса и принимает средства, но потратить ничего не может, потому что приватных ключей у него нет. Подпись выполняется отдельно — на аппаратном устройстве или офлайн-машине.
Классический пример: интернет-магазин с xpub на веб-сервере выдаёт уникальный адрес под каждый заказ. Взлом такого сервера не даёт доступа к уже полученным средствам — только к тем, которые пришли бы в будущем.
Закалённая деривация
У удобства xpub есть цена, и она серьёзная.
xpub содержит код цепочки. Если хоть один дочерний приватный ключ утечёт, то вместе с этим кодом цепочки он раскрывает все остальные дочерние приватные ключи. Хуже: дочерний приватный ключ вместе с родительским кодом цепочки позволяет вычислить родительский приватный ключ.
Против этого придумана закалённая деривация (hardened). Она рвёт связь между родительским публичным ключом и дочерним кодом цепочки: на вход хеш-функции подаётся родительский приватный ключ вместо публичного. Получается «противопожарная стена» — код цепочки такой ветки уже нельзя использовать для компрометации родителя или соседей.
Спецификация BIP-32 формулирует это как свойство, которого у стандарта намеренно нет: имея родительский расширенный публичный ключ и любой незакалённый дочерний приватный ключ, вычислить родительский приватный ключ не трудно. Знание родительского xpub плюс одного незакалённого потомка равносильно знанию родительского расширенного приватного ключа — то есть всех ключей поддерева.
Отсюда практика: дети первого уровня всегда выводятся закалённой деривацией, чтобы защитить мастер-ключи, а на уровне счёта закалённость гарантирует, что утечка ключей одного счёта не компрометирует ни мастер-ключ, ни соседние счета. Общее правило — если вы хотите пользоваться удобством xpub, выводите его от закалённого родителя, а не от обычного.
Как отличить закалённый индекс
Индекс — 32-битное целое, и диапазон поделён надвое:
| Диапазон индексов | Вид деривации |
|---|---|
0 … 2³¹−1 (0x0–0x7FFFFFFF) | обычная |
2³¹ … 2³²−1 (0x80000000–0xFFFFFFFF) | закалённая |
Чтобы не писать восьмизначные шестнадцатеричные числа, закалённые индексы отображают с нуля, но со штрихом. Первый обычный ребёнок — 0, первый закалённый (индекс 0x80000000) — 0'. Запись i' означает 2³¹ + i.
В обычном ASCII штрих заменяют апострофом или буквой h. Там, где апостроф имеет специальное значение для командной оболочки — например, в дескрипторах, — рекомендуется писать h.
Отпечаток ключа (XFP)
Расширенный ключ можно идентифицировать по HASH160 (RIPEMD-160 после SHA-256) от сериализованного публичного ключа K, игнорируя код цепочки. Это ровно та же операция, что даёт данные обычного биткоин-адреса — но представлять идентификатор в base58 не рекомендуется, чтобы его не приняли за адрес.
Первые 32 бита этого идентификатора и называются отпечатком ключа (key fingerprint). Отсюда те самые восемь шестнадцатеричных символов вида 8D9CCED8, которые устройство показывает после создания кошелька.
Отпечаток не секрет и не средство защиты — он служит быстрым способом сопоставить родительский и дочерний узлы в программах. Более того, спецификация прямо предупреждает: программы обязаны быть готовы к коллизиям, а внутри себя могут использовать полный 160-битный идентификатор. Практическая ценность отпечатка в другом: он мгновенно показывает, тот ли ключ перед вами — например, применилась ли парольная фраза, которая порождает совершенно другой кошелёк и другой отпечаток.
Отпечаток родителя входит и в сериализацию расширенного ключа. Структура занимает 78 байт: 4 байта версии, 1 байт глубины, 4 байта отпечатка родителя (нули для мастер-ключа), 4 байта номера ребёнка, 32 байта кода цепочки и 33 байта данных ключа. После добавления контрольной суммы и кодирования в Base58 получается строка ровно в 111 символов.
Путь деривации
Ключи в дереве адресуются «путём»: уровни разделяются косой чертой.
- Путь, начинающийся с
m, ведёт к приватному ключу, выведенному из мастер-приватного. - Путь с заглавной
M— к публичному ключу, выведенному из мастер-публичного.
Родословная читается справа налево: m/x/y/z — это z-й ребёнок ключа m/x/y, который является y-м ребёнком m/x, который является x-м ребёнком m.
| Путь | Какой ключ описывает |
|---|---|
m/0 | первый (0) дочерний приватный ключ от мастер-ключа |
m/0/0 | первый внук по линии первого ребёнка |
m/0'/0 | первый обычный внук от первого закалённого ребёнка |
m/1/0 | первый внук по линии второго ребёнка |
Гибкость дерева обернулась проблемой: у каждого расширенного ключа 4 миллиарда детей (2 миллиарда обычных и 2 миллиарда закалённых), глубина не ограничена, и переносить кошельки между реализациями стало практически невозможно — вариантов внутренней организации бесконечно много.
Пять уровней BIP-44
Решение предложили два стандарта. Мотивировка BIP-43 сформулирована жёстко: BIP-32 оставляет реализациям слишком много степеней свободы, поэтому две программы могут обе называться «BIP-32-совместимыми» и при этом порождать несовместимые структуры — от чего заявление о совместимости становится бессмысленным.
BIP-43 объявил первый закалённый индекс назначением (purpose) дерева: кошелёк использует одну-единственную ветку первого уровня, и её номер задаёт структуру всего остального. Схемам предлагается брать номер назначения равным номеру собственного BIP — чтобы адреса не порождались из пересекающихся пространств. BIP-44 занял под многосчётную структуру назначение 44'.
Историческая деталь: ветка m/0'/* была занята ещё самим BIP-32 как счёт по умолчанию, до появления BIP-43.
Структура фиксирована и состоит из пяти уровней:
m / purpose' / coin_type' / account' / change / address_index
Апостроф означает, что на этом уровне применяется закалённая деривация BIP-32.
purpose' — константа, определяющая стандарт. Для BIP-44 это 44' (0x8000002C).
coin_type' — тип монеты. Один мастер-сид может обслуживать неограниченное число независимых криптовалют, и этот уровень создаёт для каждой отдельное поддерево, исключая переиспользование адресов между монетами. Биткоин — 0' (0x80000000), тестовая сеть — 1' (0x80000001). Полный реестр ведётся отдельно, в SLIP-0044.
account' — счёт. Делит пространство ключей на независимые «личности», чтобы кошелёк никогда не смешивал монеты разных счетов. Нумерация с нуля. Стандарт требует от программ не позволять создавать новый счёт, пока у предыдущего нет истории транзакций — иначе перестанет работать процедура обнаружения счетов.
change — назначение цепочки. 0 — внешняя цепочка, адреса для получения платежей, которые видны за пределами кошелька. 1 — внутренняя, адреса сдачи. Здесь и на следующем уровне применяется обычная, не закалённая деривация — именно чтобы можно было экспортировать xpub в незащищённое окружение.
address_index — порядковый номер адреса, нумерация с нуля.
Примеры:
| Путь | Что это |
|---|---|
M/44'/0'/0'/0/2 | третий публичный ключ для получения на основном биткоин-счёте |
M/44'/0'/3'/1/14 | пятнадцатый адрес сдачи на четвёртом счёте |
m/44'/0'/1'/0/0 | первый приватный ключ второго счёта |
Назначение задаёт тип адреса
Дальше стандарт размножился. Когда появились новые типы скриптов, вместо изобретения новой структуры менялось только значение purpose' — остальные четыре уровня используются ровно так, как определено в BIP-44.
| Стандарт | purpose' | Тип скрипта | Путь счёта |
|---|---|---|---|
| BIP-44 | 44' | P2PKH (legacy) | m/44'/0'/0' |
| BIP-49 | 49' | P2WPKH, вложенный в P2SH | m/49'/0'/0' |
| BIP-84 | 84' | P2WPKH (нативный SegWit) | m/84'/0'/0' |
| BIP-86 | 86' | P2TR с одним ключом (Taproot) | m/86'/0'/0' |
В таблице приведены пути для основной сети: сами BIP-49, BIP-84 и BIP-86 задают только значение purpose', а остальные уровни берут из BIP-44, где биткоин — это coin_type' = 0'. Учтите расхождение: в таблице неявных путей Mastering Bitcoin для BIP-49 указано m/49'/1'/0' — это путь тестовой сети, потому что все тестовые векторы BIP-49 сделаны на testnet.
Отсюда и берётся путь m/84'/0'/0'/0/0, который кошельки предлагают по умолчанию: это первый адрес получения на первом счёте в схеме нативного SegWit. Подробнее о самих типах скриптов — на странице типы биткоин-адресов.
Тестовые векторы стандартов показывают результат наглядно. Один и тот же сид abandon abandon … about даёт по пути m/84'/0'/0'/0/0 адрес bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu, а по пути m/86'/0'/0'/0/0 — bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr. Разные пути, один сид, ничего общего.
Префиксы расширенных ключей
Чтобы несовместимость была заметна, BIP-49 и BIP-84 ввели альтернативные номера версий при сериализации расширенных ключей:
| Стандарт | Публичный | Приватный | Версионные байты (pub / prv) |
|---|---|---|---|
| BIP-32 / BIP-44 | xpub | xprv | — |
| BIP-49 | ypub | yprv | 0x049d7cb2 / 0x049d7878 |
| BIP-84 | zpub | zprv | 0x04b24746 / 0x04b2430c |
В тестовой сети им соответствуют upub/uprv и vpub/vprv. Полный реестр версионных байтов — SLIP-0132. BIP-86 своих префиксов не вводит: его тестовые векторы используют обычные xprv и xpub.
Почему кошелёк выглядит «пустым»
Теперь главное следствие всей этой конструкции.
В дереве BIP-32 примерно четыре миллиарда ключей первого уровня, у каждого — свои четыре миллиарда детей, и так далее. Ни один кошелёк не в состоянии перебрать даже малую долю дерева. Значит, для восстановления средств недостаточно знать сид-фразу и алгоритм — нужно ещё знать, какие пути использовала программа, выдававшая вам адреса.
Отсюда прямое практическое правило: если вы ввели верную сид-фразу, а баланс нулевой — прежде чем паниковать, проверьте путь деривации и тип скрипта. Скорее всего, вы смотрите на другую ветку того же дерева.
Стандарты BIP-49, BIP-84 и BIP-86 несовместимы с предшественниками намеренно. Логика такая: если бы новые адреса подмешивались в старые счета, несовместимый кошелёк мог бы показать счёт, но потерять часть UTXO — и пользователь не заметил бы пропажу. Вместо этого счёт либо обнаруживается целиком, либо не обнаруживается вовсе. Отказ сделали заметным.
Обнаружение счетов и разрыв
При импорте сида программа обязана найти уже использованные счета. Алгоритм BIP-44:
- Вывести узел первого счёта (индекс 0).
- Вывести узел внешней цепочки этого счёта.
- Просканировать адреса внешней цепочки с учётом лимита разрыва.
- Если транзакций нет — остановиться.
- Если есть — увеличить индекс счёта и вернуться к шагу 1.
Алгоритм смотрит на историю транзакций, а не на баланс: счёт с нулевым балансом, но с историей, не останавливает поиск.
Лимит разрыва (gap limit) — максимальное число подряд идущих неиспользованных адресов, после которого кошелёк прекращает поиск. В BIP-44 он равен 20. Сканируются только внешние цепочки: на внутренние монеты приходят лишь из связанных с ними внешних.
Разрыв возникает естественно: кошелёк выдал адрес человеку, который передумал платить. Это нормально, пока за разрывом есть использованные адреса. Проблема начинается, когда неиспользованных подряд накопилось больше лимита. У кошелька три варианта, и все три с изъяном: отказаться выдавать новые адреса; выдавать их за пределами лимита — тогда другие программы с тем же xpub не увидят поступлений после разрыва; или переиспользовать старые адреса, жертвуя приватностью. Промышленные решения вроде BTCPay Server обходят это большим лимитом и ограничением скорости выставления счетов.
Неявные и явные пути
Есть два подхода к тому, откуда кошелёк узнаёт нужный путь.
Неявные пути — те самые стандартные пути из таблицы выше. Пользователю не нужно ничего запоминать: программа знает свои пути и после восстановления сама сгенерирует те же ключи. Недостаток — негибкость. При восстановлении кошелёк обязан сгенерировать ключи для всех поддерживаемых путей и просканировать цепочку по каждому. Хуже того, если новая версия программы перестала поддерживать старый путь, она не сможет предупредить пользователя, что часть средств не найдена. Та же беда в обратную сторону: старая программа не знает о новых путях.
Явные пути описывают, какие ключи с какими скриптами используются. Почти все современные реализации записывают их дескрипторами (output script descriptors, BIP 380–386 и 389). Дескриптор описывает скрипт и ключи или пути ключей для него:
pkh([d34db33f/44'/0'/0']xpub6ERA…RcEL/1/*)
Здесь d34db33f — отпечаток мастер-ключа, за ним путь до счёта, дальше сам xpub и ветка /1/* внутри него. Плата за точность — необходимость резервировать эту информацию вместе с сид-фразой. Утечка дескриптора обычно не компрометирует средства, но снижает приватность, так что некоторой защиты он требует.
Исторически кошельки для одной подписи используют неявные пути, а рассчитанные на мультиподпись и сложные скрипты всё чаще переходят на дескрипторы.
Что резервировать кроме сид-фразы
Из неявности путей следует ограничение, которое становится всё заметнее: восстановить по сид-фразе можно только то, что либо универсально (стандартный путь), либо выводится из сида (ключи).
Всё остальное сид-фраза не восстанавливает:
- Чужие публичные ключи. Алисе, Бобу и Кэрол нужны две подписи из трёх. Чтобы потратить, Алисе достаточно подписи Боба или Кэрол — но чтобы найти общие средства в цепочке, ей нужны публичные ключи обоих. Значит, каждый участник обязан хранить копию всех трёх.
- Метки и комментарии. Адресные и транзакционные метки живут только в базе кошелька и недетерминированы. Без них восстановленная история — просто список дат и сумм с пустым полем «назначение». Формат обмена метками предложен в BIP-329.
- Данные протоколов поверх Биткоина. Например, состояние каналов Лайтнинга.
Сколько энтропии нужно
Стандарты называют разные числа, и это сбивает с толку: BIP-32 допускает сид от 128 до 512 бит, BIP-39 принимает от 128 до 256.
Разбор простой:
- Расширенный приватный ключ BIP-32 — это 256-битный ключ плюс 256-битный код цепочки, всего 512 бит. Значит, существует максимум 2⁵¹² различных расширенных приватных ключей, и брать больше 512 бит энтропии бессмысленно.
- Но обычных приватных ключей — чуть меньше 2²⁵⁶, и именно они защищают ваши монеты. Взяв больше 256 бит, вы всё равно получите приватные ключи с 256 битами.
- Стойкость публичного ключа Биткоина составляет 128 бит: атакующему с классическим компьютером потребуется около 2¹²⁸ операций на эллиптической кривой, чтобы найти приватный ключ по чужому публичному. Отсюда вывод — очевидной пользы от энтропии сверх 128 бит нет.
Один бонус у большей энтропии всё же есть. Если атакующий увидел часть вашей сид-фразы, чем длиннее фраза, тем труднее ему добрать остальное. Увидев половину 128-битного кода (64 бита), он вполне может перебрать оставшиеся 64. Увидев половину 256-битного (128 бит), не сможет. Полагаться на эту защиту авторы не рекомендуют.
Пробелы
Соответствие purpose' конкретным префиксам адресов (1…, 3…, bc1q, bc1p) выводится здесь из тестовых векторов и таблицы типов скриптов; нормативные описания самих форматов адресов лежат в BIP-141, BIP-173, BIP-350 и BIP-341, которые пока не ингестированы. Также не разобрана математика secp256k1, лежащая под операциями point() и сложением точек, — она принимается здесь как данность.
Источники
- Mastering Bitcoin, 3rd edition — Chapter 5: Wallet Recovery (Andreas M. Antonopoulos, David A. Harding; CC BY-SA 4.0)
- BIP-32: Hierarchical Deterministic Wallets (Pieter Wuille; BSD-2-Clause)
- BIP-43: Purpose Field for Deterministic Wallets
- BIP-44: Multi-Account Hierarchy for Deterministic Wallets
- BIP-49: Derivation scheme for P2WPKH-nested-in-P2SH based accounts
- BIP-84: Derivation scheme for P2WPKH based accounts
- BIP-86: Key Derivation for Single Key P2TR Outputs