В последнее время я работал над Освоением Биткойна, реализуя как можно больше примеров из книги на Эликсире.
Я был поражен тем, насколько хорошо Elixir справился с реализацией алгоритмов, используемых для работы с биткойн-ключами и адресами. Elixir поставляется со всеми инструментами, необходимыми для создания криптографически безопасного закрытого ключа и преобразования его в строку публичного адреса.
Давайте рассмотрим процесс шаг за шагом и создадим собственный модуль Elixir для генерации закрытых ключей и публичных адресов.
Что такое закрытые ключи и общедоступные адреса?
Закрытый ключ Биткойн — это просто случайное число из двухсот пятидесяти шести битов. Как следует из названия, этот номер предназначен для конфиденциальности.
Из каждого закрытого ключа может быть сгенерирован общедоступный биткойн-адрес. Биткойн может быть отправлен на этот публичный адрес кем угодно в мире. Однако только владелец закрытого ключа может создать подпись, которая позволит ему получить доступ к хранящимся там биткойнам.
Давайте воспользуемся Elixir, чтобы сгенерировать криптографически безопасный закрытый ключ, а затем сгенерируем его наиболее простой соответствующий публичный адрес, чтобы мы могли получить немного биткойнов!
Извлечение закрытого ключа из воздуха
Как я упоминал ранее, закрытый ключ Биткойн — это просто случайное двухсотпятидесятишестибитное число. Другими словами, закрытый ключ может быть любым числом от 0
до 2^256
.
Однако не все случайные числа создаются одинаково. Нам нужно быть уверенными, что мы генерируем наше случайное число из криптографически безопасного источника энтропии. К счастью, Elixir предоставляет функцию Erlang :crypto.strong_rand_bytes/1
, которая позволяет нам легко генерировать список действительно случайных байтов.
Давайте используем :crypto.strong_rand_bytes/1
в качестве основы для нашего генератора закрытых ключей. Мы начнем с создания нового модуля PrivateKey
и функции generate/0
, не принимающей аргументов:
defmodule PrivateKey do
def generate
end
Внутри нашей функции generate/0
мы будем запрашивать 32
случайных байтов (или 256
битов) из :crypto.strong_rand_bytes/1
:
def generate do
:crypto.strong_rand_bytes(32)
end
Это дает нам случайный набор 32
байтов, который, если рассматривать его как целое число без знака, находится в диапазоне от 0
до 2^256 - 1
.
К сожалению, мы не совсем закончили.
Проверка нашего закрытого ключа
Чтобы гарантировать, что наш закрытый ключ трудно угадать, Группа стандартов эффективной криптографии рекомендует выбирать закрытый ключ между числом 1
и числом чуть меньше 1.158e77
:
Мы можем довольно легко добавить эту проверку правильности, добавив предоставленную SECG верхнюю границу в качестве атрибута в наш модуль PrivateKey
:
@n :binary.decode_unsigned(<<
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE,
0xBA, 0xAE, 0xDC, 0xE6, 0xAF, 0x48, 0xA0, 0x3B,
0xBF, 0xD2, 0x5E, 0x8C, 0xD0, 0x36, 0x41, 0x41
>>)
Далее мы добавим в наш модуль функцию valid?/1
, которая возвращает true
, если предоставленный секретный ключ попадает в этот диапазон, и false
, если он не входит:
defp valid?(key) when key > 1 and key < @n, do: true
defp valid?(_), do: false
Прежде чем мы передадим наш закрытый ключ в нашу функцию valid?/1
, нам нужно преобразовать его из тридцатидвухбайтового двоичного файла в целое число без знака. Давайте добавим третью головку функции valid?/1
, которая делает именно это:
defp valid?(key) when is_binary(key) do
key
|> :binary.decode_unsigned
|> valid?
end
Мы завершим нашу проверку, передав сгенерированный закрытый ключ в нашу новую функцию valid?/1
. Если ключ действителен, мы вернем его. В противном случае мы сгенерируем новый закрытый ключ и повторим попытку:
def generate do
private_key = :crypto.strong_rand_bytes(32)
case valid?(private_key) do
true -> private_key
false -> generate
end
end
Теперь мы можем вызвать PrivateKey.generate
, чтобы сгенерировать новый приватный биткойн-ключ!
От закрытого ключа к открытому ключу…
Самый простой процесс превращения закрытого ключа Биткойн в общий публичный адрес состоит из трех основных шагов. Первый шаг — преобразовать наш закрытый ключ в открытый ключ с помощью криптографии на эллиптических кривых.
Начнем с добавления новой функции to_public_key/1
в наш модуль PrivateKey
:
def to_public_key(private_key)
В нашей функции to_public_key/1
мы будем использовать функцию Erlang :crypto.generate_key
, чтобы подписать наш private_key
с помощью эллиптической кривой. Мы специально будем использовать кривую :secp256k1
:
:crypto.generate_key(:ecdh, :crypto.ec_curve(:secp256k1), private_key)
Мы используем генерацию ключа эллиптической кривой в качестве функции лазейки, чтобы обеспечить секретность нашего закрытого ключа. Нам легко сгенерировать наш открытый ключ из нашего закрытого ключа, но произвести обратное вычисление и сгенерировать наш закрытый ключ из нашего открытого ключа почти невозможно.
Функция :crypto.generate_key
возвращает кортеж из двух элементов. Первый элемент в этом кортеже — наш открытый ключ Биткойн. Мы вытащим его с помощью функции Elixir elem/1
:
:crypto.generate_key(:ecdh, :crypto.ec_curve(:secp256k1), private_key)
|> elem(0)
Возвращаемое значение представляет собой 65-байтовый двоичный файл, представляющий наш открытый ключ!
… Открытый ключ к открытому хэшу …
Как только мы получим наш открытый ключ в памяти, нашим следующим шагом в преобразовании его в публичный адрес будет его хеширование. Это дает нам так называемый «открытый хэш» нашего открытого ключа.
Давайте создадим новую функцию to_public_hash/1
, которая принимает наше private_key
в качестве аргумента:
def to_public_hash(private_key)
Мы начнем процесс хэширования, превратив наш private_key
в открытый ключ с вызовом to_public_key
:
private_key
|> to_public_key
Затем мы передаем наш открытый ключ через две функции хэширования: SHA-256, а затем RIPEMD-160:
private_key
|> to_public_key
|> hash(:sha256)
|> hash(:ripemd160)
Биткойн использует алгоритм хеширования RIPEMD-160, потому что он производит короткий хэш. Промежуточное хеширование SHA-256 используется для предотвращения ненадежности из-за неожиданных взаимодействий между нашим алгоритмом подписи на эллиптической кривой и алгоритмом RIPEMD.
В этом примере hash/1
— это вспомогательная функция, обертывающая :crypto.hash
в Erlang.
defp hash(data, algorithm), do: :crypto.hash(algorithm, data)
Переключение аргументов на :crypto.hash
таким образом позволяет нам легко передать наши данные через помощника hash/1
.
… И публичный хэш в публичный адрес
Наконец, мы можем преобразовать наш общедоступный хэш в полноценный биткойн-адрес с помощью кодирования Base58Check хэша с байтом версии, соответствующим сети, в которой мы используем адрес.
Давайте добавим функцию to_public_address/2
в наш модуль PrivateKey
:
def to_public_address(private_key, version \\ <<0x00>>)
Функция to_public_address/2
принимает в качестве аргументов байты private_key
и version
. По умолчанию version
имеет значение <<0x00>>
, что указывает на то, что этот адрес будет использоваться в действующей сети Биткойн.
Чтобы создать биткойн-адрес, мы начинаем с преобразования нашего private_key
в общедоступный хеш с вызовом to_public_hash/1
:
private_key
|> to_public_hash
Все, что осталось сделать, это закодировать Base58Check полученный хэш с предоставленным version
байтом:
private_key
|> to_public_hash
|> Base58Check.encode(version)
После закладки основы последние части головоломки легко встают на свои места.
Использование нашего творения
Теперь, когда мы можем генерировать криптографически безопасные закрытые ключи и преобразовывать их в публикуемые общедоступные адреса, мы в деле.
В прямом смысле!
Давайте сгенерируем новый закрытый ключ, преобразуем его в соответствующий публичный адрес и попробуем в тестовой сети Биткойн. Мы начнем с создания нашего закрытого ключа:
private_key = PrivateKey.generate
Это дает нам 32-байтовый двоичный файл. Если бы мы захотели, мы могли бы Base58Check закодировать это с помощью testnet version
byte of 0xEF
. Это известно как Формат импорта кошелька или WIF нашего закрытого ключа Биткойн:
Base58Check.encode(private_key, <<0xEF>>)
Как следует из названия, преобразование нашего закрытого ключа в WIF позволяет нам легко импортировать его в большинство программ для биткойн-кошельков:
Импорт нашего тестового закрытого ключа.
Затем давайте преобразуем наш закрытый ключ в публичный адрес тестовой сети, используя version
байт 0x6F
:
PrivateKey.to_public_address(private_key, <<0x6F>>)
Теперь, когда у нас есть публичный адрес, давайте найдем сборник тестовой сети и отправим несколько tBTC на наш только что сгенерированный адрес! После инициирования транзакции с помощью нашего крана мы должны увидеть, как наш биткойн поступает на наш адрес либо в обозревателе блокчейна, либо в программном обеспечении нашего кошелька.
Победа!
Последние мысли
Elixir, благодаря своему наследию Erlang, поставляется с множеством инструментов, которые делают этот вид хеширования, подписи и байтового пюре прогулкой в парке.
Я рекомендую вам проверить наш модуль PrivateKey
на Github, чтобы лучше понять простоту кода, который мы написали сегодня. В общем, я очень доволен результатом.
Если вам понравилась эта статья, я настоятельно рекомендую вам ознакомиться с книгой Mastering Bitcoin. Если вам действительно понравилась эта статья, не стесняйтесь отправить несколько биткойнов на этот адрес, который я сгенерировал с помощью нашего нового модуля PrivateKey
:
1HKz4XU7ENT46ztEzsT83jRezyiDjvnBV8
Следите за новостями, связанными с биткойнами, пока я работаю над Освоением биткойнов!
Первоначально опубликовано на сайте www.petecorey.com 22 января 2018 г.