Как Electrum создает пару ключей из семени?

Electrum использует начальное число из 12 слов для генерации пары ключей, а затем иерархически генерирует адреса из этой пары ключей.

Я знаю, как генерировать иерархические адреса из пары ключей, но чего я не понимаю, так это того, как Electrum генерирует пару ключей из семени. Я понимаю, что начальное число состоит из слов, поступающих из списка слов длиной 2048 слов, но после этого мне непонятно, как оно начинает генерировать последовательность адресов.


Я хотел бы сделать генерацию HD либо на Java/C++/Python, либо на C#. Если я смогу понять этот процесс, я смогу воспроизвести его самостоятельно и сгенерировать иерархические адреса из семени.


РЕДАКТИРОВАТЬ:

Через день я нахожу эту реализацию Java... Спасибо за ответы!

https://github.com/harningt/atomun-mnemonic

Ответы (1)

Кошельки, созданные Electrum 1.x, содержат сид-слова, содержащие 12 слов (24 слова также возможны для сид-кодов, созданных пользователем ). При заданном массиве seed_words такой длины, отсчитываемом от нуля, этот псевдокод вычисляет master_private_key:

i = 0
while i < length(seed_words):
    # convert each word into an int in the range [0,1625]
    # based on the word's position in the sorted word list
    seed_ints[i] = lookup_seed_word(seed_words[i])
    i = i + 1

num_words  = 1626
num_words2 = num_words * num_words
seed_hex_str = ""
i = 0
while i < length(seed_words):
    # (hex8 converts an int into an ASCII string of
    # exactly 8 zero-padded lowercase hex digits;
    # % is the integer remainder operator
    seed_hex_str = seed_hex_str + hex8( seed_ints[i    ]
                    + num_words  * (   (seed_ints[i + 1] - seed_ints[i    ]) % num_words )
                    + num_words2 * (   (seed_ints[i + 2] - seed_ints[i + 1]) % num_words ))
    i = i + 3

unstretched_seed = ascii_string_to_byte_array(seed_hex_str)
seed = byte_array()  # an empty byte array
i = 0
while i < 100000:
    # sha256 operates on and produces byte arrays
    seed = sha256(seed + unstretched_seed)
    i = i + 1

master_private_key = byte_array_to_int(seed, order=big_endian)

Если вам интересно, кажется, что причина, по которой вычисление seed_ints выглядит слишком сложным, может заключаться в том, чтобы избежать нарушения патента .

Для сравнения, кошельки, созданные Electrum 2.x, обычно содержат сид-слова, содержащие 13 слов, однако иногда их может быть меньше. (Технически возможно создать начальные числа практически любой длины, которые будут приняты при восстановлении кошелька Electrum 2.x.) Вот псевдокод, который вычисляет расширенный главный закрытый ключ BIP-32:

# Electrum 2.x doesn't separate mnemonic words with spaces in sentences for any CJK
# scripts when calculating the checksum or deriving a binary seed (even though this
# seems inappropriate for some CJK scripts such as Hiragana as used by the ja wordlist)
if language is CJK:
    space = ""
else:
    space = " "

seed_phrase = ""
i = 0
do:
    word = seed_words[i]
    normalize_unicode(word, normalization=nfkd)
    remove_unicode_combining_marks(word)  # e.g. accent marks
    seed_phrase = seed_phrase + word
    i = i + 1
    if i ≥ length(seed_words):
        exit-loop
    seed_phrase = seed_phrase + space

seed_utf8 = unicode_to_byte_array(seed_phrase, format=utf8)

if hmac_sha512(key="Seed version", message=seed_utf8)[0] ≠ 1:
    fail("invalid checksum")

stretched_seed = pbkdf2_hmac_sha512(password=seed_utf8, salt="electrum", iterations=2048, output_length=64)
seed_bytes = hmac_sha512(key="Bitcoin seed", message=stretched_seed)

private_key        = byte_array_to_int(seed_bytes[0..31], order=big_endian)
chain_code_bytes   = seed_bytes[32..63]
master_private_key = create_bip32_extended_private_key(private_key, chain_code_bytes)

BIP-39 , альтернативный метод получения, похож на Electrum 2.x, но не идентичен.

«seed = sha256 (seed + unstretched_seed)» или seed = sha256 (seed) + sha256 (unstretched_seed))»? Я не знаю о методе SHA256, который принимает двухбайтовые массивы.
Вы правы, это не так. «+» предназначался для передачи конкатенации. IOW, исходный unstretched_seed добавляется к результату последнего sha256 до того, как его хеш будет взят на каждой итерации. Более конкретный пример см. здесь: github.com/gurnec/btcrecover/blob/…
@MrJones К вашему сведению, я исправил ошибку в псевдокоде Electrum 1.x: num_words было неправильным. Прости за это.
Я подтверждаю дело об обходе патента