Генерация ключа в HD-кошельках с использованием расширенного закрытого ключа и усиленная деривация

Я читаю книгу Mastering Bitcoin и запутался в выводах ключей кошелька в главе 4. Ключи, адреса, кошельки .

В книге сначала упоминается получение закрытого дочернего ключа, где дочерний закрытый ключ получается из трех входных данных: (родительский открытый ключ, ранее полученный из родительского закрытого ключа, кода родительской цепочки, индекса).

Далее в книге обсуждаются расширенные ключи и упоминаются два типа: расширенные закрытые ключи и расширенные открытые ключи.

Расширенные открытые ключи используются для получения дочерних открытых ключей из открытых ключей родителей, чтобы избежать раскрытия закрытых ключей, что делает их более безопасными. Это блок-схема расширенного открытого ключа .

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

Однако, хотя расширенный открытый ключ не раскрывает закрытый ключ, его использование по-прежнему рискованно, поскольку он раскрывает коды цепочки. В случае утечки закрытого ключа их можно использовать вместе для получения других дочерних ключей.

Наконец, в книге предлагается использовать защищённый ключ , описание которого, по моему мнению, совпадает с описанием расширенного закрытого ключа.

Мой вопрос: первый вопрос: является ли расширенный закрытый ключ тем же самым, что и создание защищенного ключа?

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

Ответы (3)

Здесь много путаницы, в основном фрагменты всей схемы иерархического детерминированного вывода, и, наконец, два вопроса, которые, кажется, указывают на то, что в ней упущен какой-то момент. Ответ на первый вопрос — нет. Второй вопрос более интересен:

Начнем с расширенных ключей, в частности ключей BIP32. Подобно закрытым и открытым ключам, расширенные ключи могут быть либо «закрытыми», либо «открытыми». Я поместил оба в кавычки, потому что оба типа расширенных ключей содержат личную информацию. По крайней мере, достаточно для отслеживания использования ключа. Этот механизм используется аппаратными кошельками и программными кошельками «только для просмотра» на ПК.

Расширенный ключ — это всего лишь сериализация нескольких фрагментов данных в кодировке base58:

[ magic ][ depth ][ parent fingerprint ][ key index ][ chain code ][ key ]

Где keyможет быть либо открытый ключ, либо закрытый ключ. Закрытые ключи начинаются с одного 0x00байта, поэтому длина этого большого двоичного объекта остается неизменной. Расширенный ключ обычно получается путем «обхода» некоторого path, что означает, что вы должны начать свое получение с некоторого родительского расширенного ключа и последовательно получать дочерние ключи с определенными индексами, пока вы, наконец, не получите окончательный расширенный ключ в файле path. Я перестану использовать «расширенный» в этом ответе. С этого момента я буду называть расширенный закрытый ключ, xprvа расширенный открытый ключ — xpub, а иногда просто «ключи». Нерасширенные - это просто «закрытый ключ» или «открытый ключ».

xprv или xpub - magicэто 4 байта, чтобы указать сеть, к которой он принадлежит: testnet или mainnet ( tили xсоответственно), и тип ключа ( pubи prvсоответственно). Это depthбайт, который указывает, насколько глубоко xpriv или xpout находится в пути, начиная с 00глубины masterключа и увеличиваясь на единицу по мере того, как вдоль пути выполняется создание дополнительных дочерних ключей. Обратите внимание, что до сих пор единственная разница между ключами xprvи xpub, о которой я упоминал, заключалась в части prvили pubв магии. Также должно быть ясно, что xprvи xpubмогут быть на одном пути и на одной глубине. Это означает , что для такой пары xprvиxpub[ key ]часть будет иметь 32-байтовый закрытый ключ (с одним 00байтом перед ним) в xprvфайле xprv.

Отпечаток родителя — это первые 4 байта hash160открытого ключа родителя. Это означает, что даже если родитель xprvиспользовался для получения дочернего элемента xprv, он имел бы то же самое parent fingerprint, как если бы родитель xpubиспользовался для получения дочернего элемента xpub. Родительско-дочерние отношения между ключами означают, что они являются смежными в пути.

A path— это n-кортеж индексов, обычно в базе 10, разделенных знаком /. Диапазон индекса может быть между 0 и 4294967295 (или 2 ^ 32-1), где все [0,2147483647]следует за неукрепленным производным, а индексы [2147483648,4294967295]следуют за усиленным производным. Вы можете видеть, что каждая половина диапазона индексов используется для другого метода. Можно сказать, что есть два диапазона. [0,2147483647]для незащищенных ключей и [0h,2147483647h]для закаленных ключей. указывает, что индекс h(назовем его i) следует рассматривать как i + 2147483648. Вы, вероятно, с большей вероятностью увидите hнотацию в виде знака вставки ', поэтому 1' == 1h, но я не думаю, что это очень красиво, поэтому я пока ограничусь этим h.

Пример того, как выглядит путь:

m/0h/1/2h/2/1000000000

Означает m, что ключ в этом индексе является master xprvили master xpub. Маленький mозначает, что этот расширенный ключ является master xprv, а большой Mmaster xpub. Следуя предыдущим определениям, вы можете сказать, что mэто родитель ключа at 0h, а ключ at 2hявляется дочерним элементом ключа перед ним index 1. Чтобы упростить понимание, мы будем обозначать разные ключи в пути буквами {a..e}, если мы подразумеваем, что это xprvs и {A..B}если xpubs.

m / 0h / 1 / 2h / 2 / 1000000000
m   a    b   c    d   e

Путь обычно задается с индексами в базе 10, но в самом ключе они закодированы в шестнадцатеричном формате (база 16), поэтому a [ key index ]всегда составляет 4 байта с нулями в начале, если это необходимо. И главного ключа оба всегда равны нулю, поэтому depthи , и они могут достигать максимума и соответственно. Так и являются родителем и дочерним, и так являются и . of is и его индекс , а of is while его индекс равен (80000000 + 2). Последний дочерний ключ, который нужно получить, это . Можно сказать, что мы следовали по пути, начинающемуся с , из него мы получили ключ с индексом , затем изindex0000000000FFFFFFFFFFmadedepthb0200000001depthc0380000002ema0haмы получили ключ bпо индексу 1.. и так далее. Но что значит получить новый ключ?

Оставшиеся два элемента в формате расширенного ключа, родительский [ chain code ]и [ key ]используются вместе с дочерним ключом indexдля его получения. Это означает, что для наследования cмы bпередаем некоторой функции b' chain codeи ' keyи c's' index. Конкретным примером нашего bи cбудет:

b :

0488ADE4
02
5C1BD648
00000001
2A7857631386BA23DACAC34180DD1983734E444FDBF774041578E9B6ADB37C19
003C6CB8D0F6A264C91EA8B5030FADAA8E538B020F0A387421A12DE9319DC93368

c :

0488ADE4
03
BEF5A2F9
80000002
04466B9CC8E161E966409CA52986C584F07E9DC81F735DB683C3FF6EC7B1503F
00CBCE0D719ECF7431D88E6A89FA1483E02E35092AF60C042B1DF2FF59FA424DCA

Поля упорядочены, как в структуре выше. На обоих, magicговорит xprv, depthувеличивается между родительским и дочерним, fingerprintat cявляется hash160открытым ключом, который вы получили бы из закрытого ключа в b, а bs indexнаходится в первой, незащищенной половине диапазона, в то время как c' s — вторая, закаленная половина. Наконец, chain codeи keys каждого из xprvs кодируются.

Наследование chain codeand keyfor cfrom bвыполняется с помощью процесса, называемого CKDpriv, что означает получение дочернего элемента xprvот родителя xprv. В этом процессе мы использовали chain codeand keyfrom bи indexfrom c. Важный момент: мы кодировали только c после того , как получили его chain codeи keyиз того, что будет его index.

Any xprvможно использовать с CKDprivдля получения дочернего элемента xprvв любом index. Конкретный способ CKDprivвоздействия на ввод зависит от того, indexнаходится ли дочерний элемент в усиленном диапазоне или в незащищенном диапазоне. По сути, CKDprivфункция запускает HMAC-SHA512родительские chain codeи keyдочерние index. Эта функция hmac принимает два значения a key*(не путать с нашими вхождениями key, будем обозначаться как hkey) и text. Родительский chain codeиспользуется как hkey, в то время textкак состоит из родительского keyв форме закрытого ключа , если дочерний индекс находится в усиленном диапазоне, [0h,2147483647h]и в открытом ключеформируются, если индекс находится в незащищенном диапазоне. Затем он объединяется с дочерним index.

cindex находится в усиленном диапазоне, поэтому CKDprivhmac-sha512 запускается с входными данными:

HMAC-SHA512( 2A7857631386BA23DACAC34180DD1983734E444FDBF774041578E9B6ADB37C19,
             003C6CB8D0F6A264C91EA8B5030FADAA8E538B020F0A387421A12DE9319DC9336880000002 )

Который возвращает 64-байтовый хеш:

8F6154A0A82D0F68B9E5B586EA66D951DAAA071BEBD390097CC516285C791A6204466B9CC8E161E966409CA52986C584F07E9DC81F735DB683C3FF6EC7B1503F

32 байта в правой половине этого хеша 04466B9C...C7B1503Fстановятся дочерними ( cздесь) chain code, а 32 байта слева используются для «настройки», что означает просто «добавление mod n» к родительскому ключу, в этом примере:

  8F6154A0A82D0F68B9E5B586EA66D951DAAA071BEBD390097CC516285C791A62
+
  3C6CB8D0F6A264C91EA8B5030FADAA8E538B020F0A387421A12DE9319DC93368
=
  CBCE0D719ECF7431D88E6A89FA1483E02E35092AF60C042B1DF2FF59FA424DCA   mod n
  • Я не писал здесь 00предваряющие байты в ключах, потому что это просто добавление чисел, но эти нулевые байты очень важны для хеш-функции, поэтому я специально включил их туда.

Теперь, когда у нас есть и c( в форме закрытого ключа), мы хотели бы на самом деле закодировать его, чтобы он был пригодным для использования . Чтобы получить from , нам нужно знать открытый ключ from . Поскольку он находится в форме закрытого ключа, нам придется выполнить умножение:chain codekeycxprvfingerprintbkeyb

CBCE0D719ECF7431D88E6A89FA1483E02E35092AF60C042B1DF2FF59FA424DCA * G
= 03501E454BF00751F24B1B489AA925215D66AF2234E3891C3B21A52BEDB3CD711C

Возьмите hash160этот открытый ключ, и возвращенный хэш будет BEF5A2F9A56A94AAB12459F72AD9CF8CF19C7BBE. Первые четыре байта являются bотпечатком пользователя: BEF5A2F9. Кодирование остальной части cлегко. Начните с волшебства xprv, так как мы получили дочерний элемент xprv, увеличьте глубину bна единицу, затем fingerprint. Следующий cкодируется index. Мы вывели index 2h, так что это будет 80000002, а затем новый chain codeи keyтот, который мы получили от CKDpriv.

Это в основном то , что представляет собой закаленная деривация. Закрытый ключ родителя и код цепи используются для получения дочернего ключа в некотором защищенном индексе. Что, если мы хотим вывести d? Это index 2, так что это незащищенный индекс. Это второй случай CKDpriv.

Разница в том, что используется для textпараметра HMAC-SHA512функции. Вместо того, чтобы использовать keyформу закрытого ключа родителя, мы используем форму открытого ключа, поэтому для получения dиндекса 2от cмы сначала находим открытый ключ c:

CBCE0D719ECF7431D88E6A89FA1483E02E35092AF60C042B1DF2FF59FA424DCA * G
= 0357BFE1E341D01C69FE5654309956CBEA516822FBA8A601743A012A7896EE8DC2

Затем продолжайте выполнять те же шаги, что и выше:

HMAC-SHA512( 04466B9CC8E161E966409CA52986C584F07E9DC81F735DB683C3FF6EC7B1503F,
             0357BFE1E341D01C69FE5654309956CBEA516822FBA8A601743A012A7896EE8DC200000002 )

                            tweak                                                            chain code
437984D45C4A2F5840C65B3DC6D7274E2859AD25D092DB032C49AA4D006A426B|CFB71883F01676F587D023CC53A35BC7F88F724B1F8C2892AC1275AC822A3EDD

* обратите внимание, что 00это не добавляется к text, так как это открытый ключ.

  437984D45C4A2F5840C65B3DC6D7274E2859AD25D092DB032C49AA4D006A426B
+
  CBCE0D719ECF7431D88E6A89FA1483E02E35092AF60C042B1DF2FF59FA424DCA
=
  0F479245FB19A38A1954C5C7C0EBAB2F9BDFD96A17563EF28A6A4B1A2A764EF4   mod n

hash160( 0357BFE1E341D01C69FE5654309956CBEA516822FBA8A601743A012A7896EE8DC2 )

finger
   print
EE7AB90C|DE56A8C0E2BB086AC49748B8DB9DCE72

Остальное легко, и мы можем закодировать:

d :

0488ADE4
04
EE7AB90C
00000002
CFB71883F01676F587D023CC53A35BC7F88F724B1F8C2892AC1275AC822A3EDD
000F479245FB19A38A1954C5C7C0EBAB2F9BDFD96A17563EF28A6A4B1A2A764EF4

Разница между этими двумя методами получения потомков xprvтонкая, но важная. Это позволяет CKDpub, которая является функцией для получения потомков xpubот родителя xpub . CKDpubработает почти так же, как CKDprivнезащищенный вывод , но он выполняет вывод, используя сложение точек, поэтому вместо сложения целых чисел для создания дочерних закрытых ключей мы складываем точки для создания дочерних открытых ключей. Обратите внимание, как в незащищенном производном мы использовали общедоступную точку родителя для HMAC-SHA512, мы использовали в tweakкачестве добавленного значения к родительскому закрытому ключу для получения дочернего закрытого ключа, в частности, мы получили dзакрытый ключ .

Чтобы понять CKDpub, полезно сначала узнать о еще одной функции BIP32 под названием Neuter. Его цель - преобразовать xprvв xpub. Давайте «запустим» Neuterна нашем xprv d. Назовем полученный xpub D. Neuterделает две вещи xprv: 1. Заменяет magicfrom 0488ADE4на 0488B21E(заменяет xprvна xpub) 2. Заменяет закрытый ключ в поле на keyоткрытую точку того же закрытого ключа

для нашего xprv dобщедоступная точка:

0F479245FB19A38A1954C5C7C0EBAB2F9BDFD96A17563EF28A6A4B1A2A764EF4 * G
= 02E8445082A72F29B75CA48748A914DF60622A609CACFCE8ED0E35804560741D29

(это обычный процесс закрытого ключа -> открытый ключ)

так что результат:

D:

0488B21E
04
EE7AB90C
00000002
CFB71883F01676F587D023CC53A35BC7F88F724B1F8C2892AC1275AC822A3EDD
02E8445082A72F29B75CA48748A914DF60622A609CACFCE8ED0E35804560741D29

Теперь dон «кастрирован», Dоткрытый ключ закодирован, но посмотрите, как chain codeсохраняются depth, fingerprintи index. Он xpub Dнаходится в том же месте на пути, что и xprv d. Мы будем использовать chain codeand key(открытый ключ) для CKDpub, так же, как и CKDprivс незащищенной деривацией, но что касается CKDpriv, мы получили дочерний закрытый ключ, используя:

tweak + (parent private key) = child private key

ибо CKDpubмы будем использовать:

tweak*G + (parent public key) = child public key

Это работает, потому что parent public keyэто действительно справедливо (parent private key)*Gи child public keyсправедливо (child private key)*G. То есть, если мы возьмем CKDprivуравнение настройки и умножим все элементы на G, мы получим именно CKDpubуравнение настройки. CKDpubможет создавать дочерние xpubключи только в диапазоне незащищенных индексов. Это связано с тем, что информация, представленная в родительском элементе xpub, в частности открытый ключ в элементе [ key ], применяется только к незащищенному диапазону. Где CKDprivмы могли бы использовать закрытый ключ, чтобы узнать открытый ключ, мы не можем пойти другим путем. HMAC-SHA512раунд, в котором используются открытые ключи , CKDprivотносится к диапазону незащищенных индексов.

Теперь, когда мы выполнили стерилизацию dдля создания xpub D, следующим в пути будет файл eс индексом 1000000000 (или 3B9ACA00), который находится в незащищенном диапазоне, поэтому мы должны иметь возможность получить Eдочерний элемент xpubс Dпомощью CKDpub. Мы начинаем с hmac-sha512 родителя chain codeas hkeyи родителя key(открытого ключа), объединенного с дочерним Eиндексом:

HMAC-SHA512( CFB71883F01676F587D023CC53A35BC7F88F724B1F8C2892AC1275AC822A3EDD,
             02E8445082A72F29B75CA48748A914DF60622A609CACFCE8ED0E35804560741D293B9ACA00 )

                            tweak                                                            chain code
37D3E49D8ECB854CC518BBA096F46795A9707860BF0FC95E5B19278C997098D4|C783E67B921D2BEB8F6B389CC646D7263B4145701DADD2161548A8B078E65E9E

Умножьте настройку на генератор, Gчтобы мы могли настроить открытый ключ родителя, используя сложение точек:

37D3E49D8ECB854CC518BBA096F46795A9707860BF0FC95E5B19278C997098D4 * G
= 0327E992F68217BC3E88CFFC3FEAB475880145413CBE008DB22B496DF4E1C3F864  <- tweak*G

Добавьте настройку к родительской точке. Результатом является открытый ключ ребенка:

  0327E992F68217BC3E88CFFC3FEAB475880145413CBE008DB22B496DF4E1C3F864
+
  02E8445082A72F29B75CA48748A914DF60622A609CACFCE8ED0E35804560741D29
=
  022A471424DA5E657499D1FF51CB43C47481A03B1E77F951FE64CEC9F5A48F7011

Получить отпечатки пальцев родителей:

hash160(02E8445082A72F29B75CA48748A914DF60622A609CACFCE8ED0E35804560741D29) = D880D7D8....

Наконец, мы можем закодировать E:

0488B21E
05
D880D7D8
3B9ACA00
C783E67B921D2BEB8F6B389CC646D7263B4145701DADD2161548A8B078E65E9E
022A471424DA5E657499D1FF51CB43C47481A03B1E77F951FE64CEC9F5A48F7011

Выполнив стерилизацию d, Dа затем производя E, мы можем сказать, что наш путь теперь выглядит так:

m / 0h / 1 / 2h / 2 / 1000000000
m / a  / b / c  / D / E

Или мы можем использовать N()нотацию (для Neuter), чтобы показать, где CKDpubона использовалась, но я думаю, что это менее красиво.m / a / b / c / N(d / e)

Итак, чтобы резюмировать ваш вопрос, существует 3 разных метода вывода: два с использованием закрытых ключей и один с использованием открытых ключей:

  1. CKDprivполучить дочерний xprvэлемент по усиленному индексу
  2. CKDprivдля получения дочернего xprvэлемента по нежесткому индексу
  3. CKDpubполучить дочерний xpubэлемент по незащищенному индексу
сказочный ответ! спасибо +1 миллион
Я думал, что расширенный ключ был просто (ключ + код цепочки)?
Я думаю, что сжег 5 ккал, следуя этому ответу. Шутки в сторону, я искренне надеюсь, что вы планируете написать книгу О'Рейли о криптокошельках.
Вычисление открытого ключа secp256k , вероятно, отключено: 1% % echo bx ec-to-public 0357bfe1e341d01c69fe5654309956cbea516822fba8a601743a012a7896ee8dc2или 2% bx ec-multiply0357bfe1e341d01c69fe5654309956cbea516822fba8a601743a012a7896ee8dc2
есть ли команда bx для суммирования между (tweak * g) и открытым ключом? ec-добавить не работает

Я тоже с трудом пытаюсь понять материал там. Вот что я понимаю:

Простое понимание состоит в том, что для получения расширенного закрытого ключа и расширенного получения открытого ключа все они имеют одинаковые входные данные (родительский открытый ключ и узел родительской цепочки). В результате у них будут одинаковые левые 256 бит и правые 256 бит.

Разница в том, что

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

А для получения расширенного открытого ключа левые 256 бит добавляются к родительскому открытому ключу для создания дочернего открытого ключа.

Оба производных расширенного ключа используют свои правые 256-битные выходные данные в качестве узла цепочки, поэтому узел цепочки одинаков для обоих производных.

Для вывода усиленного ключа ввод отличается от вышеприведенного вывода расширенного ключа.

Вместо того, чтобы использовать родительский открытый ключ, он использует родительский закрытый ключ с узлом цепочки в качестве входных данных , что приведет к другим 512-битным выходным данным. Таким образом, правильный 256-битный вывод, который используется в качестве узла цепочки, отличается от описанного выше механизма.

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

Защищенные ключи, по сути, полностью генерируют новые ключи.

Все преимущество использования иерархических ключей заключается в том, что вы можете генерировать много открытых ключей без доступа к закрытым ключам. Позже вы можете использовать тот же метод, который вы использовали для создания открытых ключей на закрытых ключах, и иметь возможность тратить средства.

Это полезно, например, если вы хотите, чтобы веб-сайт получал средства, но не терял их в случае взлома. Веб-сайт может генерировать новый открытый ключ для каждого клиента, даже не касаясь закрытых ключей, что обеспечивает более надежную защиту средств.

Закаленные ключи разрывают эту связь. Операция с усиленным ключом может изменить закрытый ключ, чтобы создать новый, но НЕ изменить открытый ключ.

Они не очень критичны, так как вы можете просто сгенерировать новый ключ и получить тот же результат, но это более удобно. Можно более-менее игнорировать закалённые ключи и просто продолжать радоваться жизни =).

Я не согласен с этим. Незащищенная деривация уязвима в ситуациях, когда закрытый ключ может быть передан совместно, а расширенный открытый ключ может быть известен. Защищенная деривация должна использоваться по умолчанию, если нет веской причины, по которой вам нужно иметь возможность генерировать открытые ключи без доступа к закрытому ключу.