Восстановление HD-кошелька из частичной сид-фразы

Я пытаюсь помочь восстановить кошелек, владелец которого записал только 11 из 12 слов в исходной фразе. Изначально я думал, что задача будет быстрой и четко сформулированной, но оказалось, что она немного сложнее, чем я предполагал, а справочного материала довольно мало. В случае, если у кого-то еще возникнет проблема, аналогичная моей, я хочу оставить этот пост с подробным описанием шагов, которые я выполнил (с рабочими примерами кода).

Кошелек, с которым я имею дело, — это Breadwallet, который, по-видимому, использует другую (более старую) стратегию вывода мнемоник-в-HD-мастер-закрытый ключ от большинства современных кошельков. На данный момент я собираюсь сосредоточиться только на восстановлении частичных фраз Breadwallet, но я планирую в конечном итоге расширить ответ, чтобы охватить также новые стратегии деривации ( BIP44 ).

Это действительно хороший материал. Как бы это работало, если бы вы использовали кошелек Electrum (2.x) из 13 слов, в котором отсутствовало только первое начальное слово? Итак, у нас есть положение, но дополнительная изменчивость различий в энтропии и т. д. с 13 против 12...
При отсутствии одного слова и известной позиции восстановить семя несложно. Контрольная сумма встроена в начальное число, поэтому очень мало слов, которые вообще могут работать (вероятно, вам нужно будет попробовать только несколько начальных значений).
Вы не должны задавать вопросы в разделе ответов.

Ответы (1)

(Язык, используемый в этом посте, — Python)

Breadwallet использует BIP39 для генерации 128-битного основного начального числа из мнемоники из 12 слов. Затем основное семя используется для создания набора кошельков/аккаунтов, содержащих цепочки адресов, с использованием BIP32 .

https://github.com/bitcoin/bips/raw/master/bip-0032/derivation.png


Во-первых, импортируйте hashlib и binascii, они нам понадобятся позже.

import hashlib
from binascii import hexlify, unhexlify

Предположим, у вас есть 11 из 12 слов в исходной фразе. Для простоты я буду использовать первые 11 слов в списке слов BIP39:

partial_seed_phrase = [ 'abandon', 'ability', 'able', 'about', 'above', 'absent', 'absorb', 'abstract', 'absurd', 'abuse', 'access' ]

Список слов содержит 2048 записей, что дает каждому слову 11 бит энтропии (2 · 11 = 2048). Всего 12 слов имеют 12*11 = 132 бита энтропии. Начальное семя HD имеет длину 128 бит, а к концу прикреплена 4-битная контрольная сумма, в результате чего общее количество битов достигает 132. Пока все хорошо.

Если мы предположим, что wordlistэто список из 2048 элементов (опущен из-за нехватки места), мы можем найти индекс (в десятичном виде) элементов в partial_seed_phrase:

mnemonic_in_decimal = map(wordlist.index, partial_seed_phrase)
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Преобразуем mnemonic_in_decimalв массив 11-битных двоичных чисел.

mnemonic_in_binary = map('{0:011b}'.format, mnemonic_in_decimal)
# ['00000000000', '00000000001', '00000000010', '00000000011', '00000000100', '00000000101', '00000000110', '00000000111', '00000001000', '00000001001', '00000001010']

Мы знаем, что в каком-то неизвестном месте этого массива отсутствует одно слово (11 бит). В неидеальных обстоятельствах нам пришлось бы проверять каждое из 12 местоположений на наличие пропущенного слова по 2048 возможных слов в каждом, что в сумме дает 24576 (12 * 2048 = 24576) потенциальных главных начальных слов.

for missing_word_position in range(0,12):
    # The missing word belongs at some index from 0-11 in the final 12-word phrase

    for wordlist_index in range(0, 2048):
        # Iterate over all possibilities for the missing word

        missing_word_binary = '{0:011b}'.format(wordlist_index)
        front_half          = ''.join(mnemonic_in_binary[0:missing_word_position])
        back_half           = ''.join(mnemonic_in_binary[missing_word_position:12])
        seed_and_checksum   = front_half + missing_word_binary + back_half

        seed     = seed_and_checksum[0:128]
        checksum = seed_and_checksum[-4:]

К счастью, у нас есть 4-битная контрольная сумма, что означает, что только одно из каждых 16 начальных чисел (2 4 = 16) будет действительным. Это означает, что в конечном итоге мы получим приблизительно 1536 основных семян (24576/16 = 1536) для проверки средств. Контрольная сумма получается из первых битов (в данном случае 4), возвращаемых путем применения хеш-функции SHA-256 к начальному числу, поэтому окончательное число действительных основных начальных значений может варьироваться, но в среднем будет составлять около 1/16 от начального числа. общее количество возможных семян.

[Подробнее будет позже, если кто-то хочет помочь написать описание или код для любого из следующих шагов, я был бы признателен! ]

Сделать:

  1. Вычислите fact_checksum из первых 4 битов sha256 (seed)
  2. сравнить контрольную сумму с фактической_контрольной суммой. Если они равны, поместите начальное значение в массив действительных основных начальных значений.
  3. Рассчитайте главный узел, которым является HMAC-SHA512 (начальное число)
  4. Рассчитать счет от мастер-узла
  5. Рассчитать цепочку кошельков от аккаунта
  6. Рассчитать первые 5 приватных ключей в цепочке кошелька
  7. Вычислите первые 5 открытых ключей из этих закрытых ключей
  8. Напишите описание и шаги для восстановления частичных фраз для кошельков BIP44.
  9. Напишите программу для локального запроса блокчейна или онлайн-интерфейса API обозревателя блокчейнов. Передайте ему объединенный список сгенерированных открытых ключей и посмотрите, есть ли у них баланс.