Небольшая статья с примерами о том, каким образом можно получить открытый ключ, имея закрытый.
В предыдущей статье мы рассматривали различные методы генерации закрытого ключа. Какой бы метод вы ни выбрали, в конце вы получите 32 байта данных. Вот ключ, который мы получили в конце той статьи:
60cf347dbc59d31c1358c8e5cf5e45b822ab85b79cb32a9f3d98184779a9efc2
В этой статье мы будем использовать этот закрытый ключ для получения как открытого ключа, так и адреса для биткоин-кошелька.
Наша задача – применить серию преобразований к закрытому ключу, чтобы получить открытый ключ, а затем и адрес кошелька. Большинство этих преобразований называются хэш-функциями. Эти хэш-функции являются односторонними преобразованиями, которые нельзя отменить. Мы не будем вдаваться в механизм самих функций — по этому вопросу есть много замечательных статей. Вместо этого мы рассмотрим, как использование этих функций в правильном порядке может помочь вам сгенерировать адрес биткоин-кошелька, который вы впоследствии сможете использовать.
Содержание статьи
Криптография с использованием эллиптической кривой
Первое, что нам нужно сделать, это применить алгоритм цифровой подписи ECDSA или Elliptic Curve к нашему закрытому ключу. Эллиптической кривой является кривая, определенная уравнением y² = x³ + ax + b с выбранной a и b. Существует целое семейство таких кривых, которые широко известны и широко используются. Для биткоина используется кривая secp256k1/. Если вы хотите узнать больше о криптографии на основе эллиптических кривых, почитайте эту статью.
Применяя ECDSA к закрытому ключу, мы получаем 64-байтовое целое число. Оно состоит из двух 32-байтовых целых чисел, которые представляют соединенные вместе точки X и Y эллиптической кривой.
Для нашего примера мы получили:
1e7bcc70c72770dbb72fea022e8a6d07f814d2ebe4de9ae3f7af75bf706902a7b73ff919898c836396a6b0c96812c3213b99372050853bd1678da0ead14487d7
На языке Python это будет выглядеть так:
private_key_bytes = codecs.decode(private_key, ‘hex’)
# Get ECDSA public key
key = ecdsa.SigningKey.from_string(private_key_bytes, curve=ecdsa.SECP256k1).verifying_key
key_bytes = key.to_string()
key_hex = codecs.encode(key_bytes, ‘hex’)
Примечание: как видно из кода, прежде чем я использовал метод на базе модуля ECDSA, я расшифровал закрытый ключ, используя кодеки. Это больше относится к Python и меньше к самому алгоритму, но я объясню, что мы здесь делаем во избежание возможной путаницы.
В Python есть как минимум два класса, которые могут хранить закрытый и открытый ключи: “str” и “bytes”. Первый – это строка, а второй – массив байтов. Криптографические методы в Python работают с классом “bytes”, принимая его в качестве входных данных и возвращая как результат.
Теперь, есть маленькая загвоздка: строка, скажем, 4f3c не равна массиву байтов 4f3c, она равна массиву байтов с двумя элементами, O<. И вот тут как раз в дело вступает codecs.decode: этот метод преобразует строку в массив байтов. Все то же самое будет и для всех криптографических манипуляций, которые мы будем приводить в этой статье.
Открытый ключ
Как только мы закончили с ECDSA, все, что нам нужно сделать, это добавить байт 0x04 в начале нашего открытого ключа. В результате мы получаем полный открытый ключ биткоина, который выглядит следующим образом:
041e7bcc70c72770dbb72fea022e8a6d07f814d2ebe4de9ae3f7af75bf706902a7b73ff919898c836396a6b0c96812c3213b99372050853bd1678da0ead14487d7
Сжатый открытый ключ
Но мы можем сделать лучше. Как вы помните, открытый ключ является некоторой точкой (X, Y) на кривой. Мы знаем кривую, и для каждого X есть только два Ys, которые определяют точку, которая лежит на этой кривой. Так зачем хранить Y? Вместо этого, давайте сохраним X и знак Y. Позже мы можем вернуть Y, если в этом будет необходимость.
Особенности заключаются в следующем: мы берем X из открытого ключа ECDSA. Теперь, мы добавим 0x02, если последний байт Y четный, и байт 0x03, если последний байт нечетный.
В нашем случае последний байт нечетный, поэтому мы добавляем 0x03, чтобы получить сжатый открытый ключ:
031e7bcc70c72770dbb72fea022e8a6d07f814d2ebe4de9ae3f7af75bf706902a7
Этот ключ содержит ту же информацию, но он почти в два раза короче, чем несжатый ключ. Клево!
Раньше программное обеспечение кошельков использовало длинные, полные версии открытых ключей, но теперь большинство из них перешло на сжатые ключи.
Шифрование открытого ключа
Теперь нам нужно сгенерировать адрес кошелька. Какой бы метод генерирования открытого ключа вы ни выбрали, он проходит ту же процедуру. Очевидно, что адреса будут отличаться. В этой статье мы рассмотрим сжатую версию.
Здесь нам нужно применить SHA-256 к открытому ключу, а затем применить RIPEMD-160 к результату. Порядок очень важен.
SHA-256 и RIPEMD-160 являются двумя хэш-функциями, и опять же, мы не будем вдаваться в детали того, как они работают.
Важно то, что теперь у нас есть 160-битное целое число, которое будет использоваться для дальнейших модификаций. Назовем это зашифрованным открытым ключом. Для нашего примера, зашифрованный открытый ключ – это 453233600a96384bb8d73d400984117ac84d7e8b
Вот как мы шифруем открытый ключ в Python:
public_key_bytes = codecs.decode(public_key, ‘hex’)
# Run SHA-256 for the public key
sha256_bpk = hashlib.sha256(public_key_bytes)
sha256_bpk_digest = sha256_bpk.digest()
# Run RIPEMD-160 for the SHA-256
ripemd160_bpk = hashlib.new(‘ripemd160’)
ripemd160_bpk.update(sha256_bpk_digest)
ripemd160_bpk_digest = ripemd160_bpk.digest()
ripemd160_bpk_hex = codecs.encode(ripemd160_bpk_digest, ‘hex’)
Добавление сетевого байта
Биткоин имеет две сети, основную и тестовую. Основной сетью является сеть, которую все люди используют для перевода монет. Тестовая сеть была создана, как вы уже догадались, для тестирования новых функций и программного обеспечения.
Мы хотим создать адрес для использования его в основной сети, поэтому нам нужно добавить 0x00 к зашифрованному открытому ключу. Результат 00453233600a96384bb8d73d400984117ac84d7e8b
. Для тестовой сети это будет 0x6f
.
Контрольная сумма
Теперь нам нужно рассчитать контрольную сумму для нашего ключа в основной сети. Идея контрольной суммы состоит в том, чтобы убедиться, что данные (в нашем случае, ключ) не были повреждены во время передачи. Программное обеспечение кошелька должно ориентироваться на контрольную сумму и отмечать адрес как недопустимый, если контрольная сумма не соответствует заявленной.
Чтобы вычислить контрольную сумму ключа, нам нужно применить SHA-256 дважды, а затем взять первые 4 байта результата. В нашем примере двойной SHA-256 — это 512f43c48517a75e58a7ec4c554ecd1a8f9603c891b46325006abf39c5c6b995
, и поэтому контрольная сумма 512f43c4
(обратите внимание, что 4 байта составляют 8 шестнадцатеричных цифр).
Контрольная сумма = первые 4 байта С
Для расчета контрольной суммы адреса используется следующий код:
# Double SHA256 to get checksum
sha256_nbpk = hashlib.sha256(network_bitcoin_public_key_bytes)
sha256_nbpk_digest = sha256_nbpk.digest()
sha256_2_nbpk = hashlib.sha256(sha256_nbpk_digest)
sha256_2_nbpk_digest = sha256_2_nbpk.digest()
sha256_2_hex = codecs.encode(sha256_2_nbpk_digest, ‘hex’)
checksum = sha256_2_hex[:8]
Получение адреса
Наконец, чтобы получить адрес, мы просто объединяем ключ основной сети и контрольную сумму. В нашем случае это выглядит так: 00453233600a96384bb8d73d400984117ac84d7e8b512f43c4
Вот и все! Это адрес кошелька для закрытого ключа, приведенного в начале статьи.
Но вы можете заметить, что что-то не так. Вы, вероятно, уже встречали биткоин-адреса, и они не выглядели так. Ну, причина в том, что они кодируются с помощью Base58 (вариант кодирования цифрового кода в виде буквенно-цифрового текста на основе латинского алфавита. Алфавит кодирования содержит 58 символов). Это немного странно.
Вот алгоритм для преобразования шестнадцатеричного адреса в адрес Base58:
def base58(address_hex):
alphabet = ‘123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz’
b58_string = ‘’
# Get the number of leading zeros
leading_zeros = len(address_hex) — len(address_hex.lstrip(‘0’))
# Convert hex to decimal
address_int = int(address_hex, 16)
# Append digits to the start of string
while address_int > 0:
digit = address_int % 58
digit_char = alphabet[digit]
b58_string = digit_char + b58_string
address_int //= 58
# Add ‘1’ for each 2 leading zeros
ones = leading_zeros // 2
for one in range(ones):
b58_string = ‘1’ + b58_string
return b58_string
Мы получаем 17JsmEygbbEUEpvt4PFtYaTeSqfb9ki1F1
, сжатый адрес биткоин-кошелька.
Вывод
Процесс генерации ключей кошелька можно разделить на четыре этапа:
- создание открытого ключа с помощью ECDSA
- шифрование ключа с помощью SHA-256 и RIPEMD-160
- расчет контрольной суммы с помощью двойной SHA-256
- кодирование ключа с помощью Base58.
В зависимости от формы открытого ключа (полный или сжатый), мы получаем разные адреса, но оба совершенно допустимы.
Вот полный алгоритм для несжатого открытого ключа:
Эллиптический открытый ключ = ECDSA (закрытый ключ)
Открытый ключ = 0х04 + эллиптический открытый ключ
Зашифрованный открытый ключ = RIPEMD-160 (SHA-256 (открытый ключ))
Зашифрованный открытый ключ основной сети = 0х00 + Зашифрованный открытый ключ
С = SHA-256 (SHA-256 (Зашифрованный открытый ключ основной сети))
Контрольная сумма = первые 4 байта С
Шестнадцатеричный адрес = Зашифрованный открытый ключ основной сети + Контрольная сумма
Адрес = Base58 (Шестнадцатеричный адрес)
Если вы хотите «поиграть» с кодом, я опубликовал его на GitHub.
Я делаю обзор о криптовалютах на Medium. Первая часть – подробное описание блокчейна.
для развития, общего даже. но тебе это не нужно. как и профильные форумы и узко-специализированные задачи..
А для чего это вообще нужно обычному пользователю криптовалют? Все ведь гораздо проще, открытый ключ — это адрес кошелька на котором лежат ваши монеты, а закрытый это грубо говоря пароль (подпись) для отправки крипты.