Я пытаюсь помочь восстановить кошелек, владелец которого записал только 11 из 12 слов в исходной фразе. Изначально я думал, что задача будет быстрой и четко сформулированной, но оказалось, что она немного сложнее, чем я предполагал, а справочного материала довольно мало. В случае, если у кого-то еще возникнет проблема, аналогичная моей, я хочу оставить этот пост с подробным описанием шагов, которые я выполнил (с рабочими примерами кода).
Кошелек, с которым я имею дело, — это Breadwallet, который, по-видимому, использует другую (более старую) стратегию вывода мнемоник-в-HD-мастер-закрытый ключ от большинства современных кошельков. На данный момент я собираюсь сосредоточиться только на восстановлении частичных фраз Breadwallet, но я планирую в конечном итоге расширить ответ, чтобы охватить также новые стратегии деривации ( BIP44 ).
(Язык, используемый в этом посте, — Python)
Breadwallet использует BIP39 для генерации 128-битного основного начального числа из мнемоники из 12 слов. Затем основное семя используется для создания набора кошельков/аккаунтов, содержащих цепочки адресов, с использованием BIP32 .
Во-первых, импортируйте 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 от начального числа. общее количество возможных семян.
[Подробнее будет позже, если кто-то хочет помочь написать описание или код для любого из следующих шагов, я был бы признателен! ]
Сделать:
Ист-Энд-Аа
ЖизньИнформация
рный