Это перевод поста пользователя webtricks. Спасибо ему за информацию!Как генерируются биткоин-адреса
Этот тема расскажет только о адресах формата P2PKH, то есть адрес, начинающийся с «1», также известный как Legacy Address. Далее я создам новую тему о том, как создаются адреса P2SH или Bech32.
Ok! Итак, начнем. Биткоин-адрес имеет:закрытый ключ и открытый ключ. Открытый ключ - это ключ, который предоставляется отправителю и всему миру. Пример: 18J6ai34uzGofUuzbiwhXJzKzdx1efDBqA. Принимая во внимание, что закрытый ключ - это ключ, который используется для доступа к средствам, полученным с помощью открытого ключа.
Возможно, вы уже знаете, что я только что сказал выше. Но вы когда-нибудь задумывались, как генерируется эта пара ключей? Давайте углубимся в тему и создадим наш собственный код для генерации пары ключей. Основным и наиболее важным компонентом Биткоин-адреса является закрытый ключ. Давайте сначала обсудим это:
Приватный ключ
Проще говоря, все может быть закрытым ключом, если оно удовлетворяет двум условиям. Условие первое: оно не должно равным 0. Во-вторых, оно должно быть ниже значения
N, определенного SECG для кривой secp256k1. Однако значение N очень и очень большое, поэтому практически каждое 256-битное число является допустимым закрытым ключом.
Теперь возникает вопрос, как сгенерировать закрытый ключ. Как я уже говорил в начале, что все может быть закрытым ключом. Например, эта строка: "я-строка для генерации закрытого ключа" может быть преобразована в закрытый ключ. Все, что вам нужно сделать, это преобразовать эту строку в 256-битное значение и проверить, что оно меньше, чем
N.
Но предлагается ли генерировать закрытый ключ таким образом? Вообще-то, нет! Говорят, что человек-худший генератор случайных чисел. Если мы используем известные фразы или числа, возможно, что кто-то другой использует точно такую же комбинацию, что может скомпрометировать закрытый ключ. Так что лучше быть в безопасности, чем сожалеть о потере и полагаться только на случайные генераторы для генерации закрытого ключа.
Но опять возникает другая проблема. Большинство генераторов, таких как Math-библиотека Javascript (функция Math.random ()), используют фиксированные шаблоны для генерации случайных чисел. Таким образом, использование таких генераторов вызовет больше неудобств, чем ключей. : D
Так что же является окончательным решением? Лучше всего использовать ключи, сгенерированные кошельками, но если вы хотите самостоятельно погрузиться в процесс, используйте безопасные генераторы, такие как randomBytes npm module в Node.js.
Хватит о закрытых ключах, давайте перейдем на bitaddress.org и сгенерируем адрес. Сначала мы создадим адрес на bitaddress.org, а затем попробуем создать его через наш собственный код, чтобы изучить математику, лежащую в основе генерации ключей.
Вот пара ключей, которую я сгенерировал. Вы можете обнаружить, что существует более одного формата для открытого и закрытого ключа. Давайте кратко обсудим их, прежде чем перейти к части кода:
1. Public Address (Публичный адрес)
P2PKH (Pay-to-Public-Key-Hash) является основной формой совершения транзакций. Для отправки биткойнов используется хеш открытого ключа . Существуют различные этапы хэширования, задействованные для генерации ключа P2PKH из шестнадцатеричного открытого ключа. Мы попытаемся рассказать об этом ниже в этой теме с примерами рабочего кода.
https://ru.bitcoinwiki.org/wiki/P2PKH2. WIF Private Key (Закрытый ключ WIF)
WIF или Wallet Import Format - это формат закрытого ключа, в который кошельки, такие как Electrum, импортируют закрытый ключ. Если вы вставите незащищенный шестнадцатеричный секретный ключ, Electrum не откроет кошелек. Вы должны конвертировать закрытый ключ в формат WIF, чтобы использовать его в кошельках. Мы также напишем код для преобразования закрытого ключа в WIF.
3. Uncompressed Public Key (Несжатый открытый ключ)
Хорошо! Поэтому я пока не обсуждал, как генерируется открытый ключ. Процесс на самом деле сложный. Возьмем специальную точку генератора, определяемую как
G по SECG, которая расположена на кривой secp256k1, то есть на одной из эллиптических кривых. Затем мы умножаем эту точку с помощью закрытого ключа. Полученное умножение даст нам две координаты, одна из которых X, а другая Y. Несжатый открытый ключ-это не что иное, как: 04 + X + Y. таким образом, первые два числа открытого ключа-это 04, Что означает, что ключ несжат. Следующие 64 символа (32 байта, так как каждые 2 символа шестнадцатеричного кода составляют 1 байт) являются координатами X, а последние 64 символа (32 байта) - координатами Y. Общая длина несжатого открытого ключа составляет 130 или 65 байт.
4. Compressed Public Key( Сжатый открытый ключ)
Поскольку можно найти координату Y, если задана координата X. Поэтому мы обычно отбрасываем координату Y из нашего открытого ключа. Таким образом, последние 64 символа удаляются. В результате сжатый открытый ключ состоит из 66 символов (32 байта). Первые два символа могут быть либо 02, либо 03 (вместо 04), а следующие 64 символа (32 байта) будут координатами X. Если значение координаты Y равно нулю, то ставится 02. Если значение координаты Y нечетное, то ставится 03. На приведенной выше фотографии значение координаты Y было нечетным, поэтому у нас есть 03 В нашем ключе.
5. Private Key Hexadecimal Form (Закрытый ключ в шестнадцатеричной форме)
Как мы обсуждали ранее, закрытый ключ должен быть 256-битным или 32-байтовым (8 бит = 1 байт), который при преобразовании в шестнадцатеричную форму должен содержать 64 символа. Таким образом, вы можете преобразовать любое значение в шестнадцатеричное, и оно будет состоять из 64 символов. Это очень удобно для нашего биткойн-кода, потому что мы будем использовать шестнадцатеричную форму закрытого ключа, чтобы начать генерировать ключи. Итак, как я говорил ранее, мы можем даже использовать строки типа «Я строка для генерации секретного ключа» для генерации секретного ключа, вот в чем секрет. Сначала мы преобразуем такие строки в шестнадцатеричные, а затем используем 64 символа шестнадцатеричных символов для генерации пары ключей.
6. Private Key Base64 Form (Форма закрытого ключа Base64)
Не очень популярный формат закрытого ключа. Но мы можем закодировать/декодировать наш закрытый ключ в Base64, используя методы преобразования.
Достаточно для началаа. Теперь давайте углубимся в описание кода и сгенерируем вышеуказанный ключ.
Поскольку я фанат Javascript (потому что я думаю, что это самый простой язык программирования и его можно использовать при разработке с полным стеком), я буду использовать JS в среде Node.JS для этого руководства. Но если вы знакомы с другим языком, вы можете легко интерпретировать мой JS-код в своем коде. Наконец, если вам совсем не нравится кодинг, тогда оставьте это, просто прочитайте текст и рисунки ниже, и я обещаю, что у вас будет лучшее представление о том, как генерируются ключи.
Перед началом давайте подготовимся. Первый шаг - создать папку. Внутри папки создайте файл с расширением .js. Имя файла может быть любым, например, index.js или app.js.
Следующим шагом является загрузка node.js на ваш компьютер. Скачать node.js очень просто, это похоже на скачивание любого другого программного обеспечения. Следующим шагом является загрузка некоторого редактора кода, я предлагаю Visual Studio Code (простой в использовании).
После выполнения вышеуказанных действий откройте папку в Visual Studio Code и перейдите к своему терминалу. В Visual Studio Code есть встроенный терминал, вы тоже можете его использовать. Если нет, вы можете использовать собственный терминал Mac или Windows, но убедитесь, что вы открыли папку в терминале. После открытия папки в Visual Studio Code и терминале выполните следующие команды в терминале, чтобы установить 2 значения для проекта:
npm init -y
npm i ripemd160 --save
npm i bs58 --save
Нам нужны три функции хеширования в нашем коде, а именно sha256, palemd160 и base58, кроме криптографии на эллиптических кривых. sha256 уже присутствует в собственной криптографической библиотеке nodejs. Мы можем либо кодировать два других самостоятельно, либо просто импортировать их. Для простоты этого руководства мы установили выше пакеты naspullmd160 и bs58 npm и будем использовать их в нашем коде. Я проверил исходный код обоих пакетов, и использовать его в коде совершенно безопасно.
Теперь давайте начнем настоящее веселье. Откройте ваш файл и начните с кода. Код в хронологическом порядке. Код шага 1 будет идти в верхней части файла, а код шага 2 начнется там, где заканчивается код шага 1 и так далее:
Step 1. Создание функций хэширования
const crypto = require('crypto');
const RIPEMD160 = require('ripemd160');
const BS58 = require('bs58');
const sha256 = input => crypto.createHash('sha256').update(input).digest();
const ripemd160 = input => new RIPEMD160().update(input).digest();
const bs58 = input => BS58.encode(input);
Ok! Итак, в первых трех строках кода мы импортировали код всех трех функций хеширования в нашем файле. Далее мы создали функции для них. Не обязательно создавать функции, но в этом случае мы должны снова и снова писать весь код всякий раз, когда нам нужно что-то хэшировать. Например, если мы не пишем эти три функции, то каждый раз, когда нам нужно создать sha256-хэш чего-то, мы должны написать
crypto.createHash ('sha256'). Update (something).digest () но с приведенным выше кодом, мы просто должны написать
sha256 (something) со следующего раза. Здорово? Давайте двигаться дальше.
Step 2. Создание функции эллиптической кривой
const generateECPoints = privateKey => {
const Pcurve = BigInt('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F');
const Gx = BigInt('55066263022277343669578718895168534326250603453777594175500187360389116729240');
const Gy = BigInt('32670510020758816978083085130507043184471273380659243275938904335757337482424');
const G = [Gx, Gy];
const modInverse = (a, n) => {
a = (a % n + n) % n
const dArray = [];
let b = n;
while(b) {
[a, b] = [b, a % b];
dArray.push({a, b});
}
if (a !== BigInt(1)) {
return null;
}
let x = BigInt(1);
let y = BigInt(0);
for(let i = dArray.length - 2; i >= 0; --i) {
[x, y] = [y, x - y * BigInt(dArray[i].a / dArray[i].b)];
}
return (y % n + n) % n;
}
const modOf = (a,b) => {
const r = ((a % b) + b)% b;
return r;
}
const ECAdd = (a,b) => {
const lamAdd = modOf((b[1] - a[1]) * BigInt(modInverse(b[0] - a[0], Pcurve)), Pcurve);
const x = modOf((lamAdd*lamAdd - a[0] - b[0]), Pcurve);
const y = modOf((lamAdd*(a[0] - x) - a[1]), Pcurve);
return [x, y];
}
const ECDouble = a => {
const lamda = modOf(((BigInt(3)*a[0]*a[0])*(modInverse(BigInt(2)*a[1], Pcurve))), Pcurve);
const x = modOf((lamda*lamda - BigInt(2)*a[0]), Pcurve);
const y = modOf((lamda*(a[0] - x) - a[1]), Pcurve);
return [x, y];
};
const ECMultiply = (genPoint, pvtKey) => {
const scalarBinary = BigInt('0x'+pvtKey).toString(2);
let GP = genPoint;
for (let i=1; i < scalarBinary.length; i++) {
GP = ECDouble(GP)
if (scalarBinary[i] === '1') {
GP = ECAdd(GP, genPoint);
}
}
return GP;
}
return ECMultiply(G, privateKey);
}
Приведенный выше код является моей версией умножения эллиптических кривых. Это пример чистого кодирование эллиптической кривой на Javascript, которое вы найдете в Интернете. Я думаю, что было бы неуместно объяснять весь приведенный выше код в этой теме, так как основная цель этой темы - генерация пары ключей. Так что пока используйте приведенный выше код как есть. Я создам отдельную ветку для криптографии на эллиптических кривых через 3-4 дня и объясню тот же код в этой ветке.
Step 3. Генерация координат X и Y из открытого ключа из вышеуказанной функции и закрытого ключа
const privateKey = "6EBD5FAB742ED0734B37C63BD2A3CE8797FE4AC63C9A99781F8BEDDF6307094E";
const publicKey = generateECPoints(privateKey);
На этом шаге мы взяли шестнадцатеричное значение секретного ключа (5-й элемент изображения) и поместили его в функцию generateECPoints, созданную на шаге 2. Это даст нам координаты X и Y открытого ключа, которые будут выглядеть следующим образом:
[26552980488606060638326679080566574626825610331305555186819497546906082384636n, 106820354128014061768597493909158935631153585355120117243602895828064095418195n]
Вы можете заметить
n в конце каждой координаты. Это
n означает, что мы имеем дело с очень большими числами, известными как большие целые числа в Javascript. Также вы можете заметить, что эти координаты не соответствуют X и Y на изображении выше. Ну, мы сгенерировали числа на данный момент. Мы должны преобразовать их в шестнадцатеричные числа, чтобы получить несжатый ключ и сжатый ключ. Давайте сделаем это на следующем шаге.