Author

Topic: Как генерируются биткоин-адреса? (Read 408 times)

legendary
Activity: 2436
Merit: 1849
Crypto for the Crypto Throne!
Привлекло внимание, поэтому решил почитать оригинал.

Перлы начинаются с самого начала:
Public Key is the key that is shared to the sender and the world. Example: 18J6ai34uzGofUuzbiwhXJzKzdx1efDBqA.

Если вы не поняли в чем юмор, значит вам самим стоит подтянуть основы.

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

Да, такие люди как A-bolt или даже такие васяны как я, уже не обращают на такое внимание, потому что для них понятно что публичный ключ это 32 байта и тд тд, а вот то, что в "примере", это адрес, коротко говоря двойной хэш публичного ключа + чексума который закодирован в base58.

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

Когда разберусь с машиной, то отпишу в оригинальной теме. К zasad-у вопросов на самом деле никаких, он все таки переводчик.
sr. member
Activity: 1316
Merit: 420
KTO EC/\U HUKTO?
Биткоину стукнуло 10 лет, а они всё пишут инструкции как адреса генерируются и даже умудряются заблудиться в терминологии. Для кого этот перевод и для кого оригинал? Чего в этой информации нового что сектанты её дружно замеритили? Сколько ещё лет сообщество намеревается жевать букварь? Cool
legendary
Activity: 2317
Merit: 2318
A-Bolt, достаточно было написать о неточностях перевода  Smiley
Правки внесены.

Глянул английский оригинал. Про два компонента открытого ключа - это действительно фантазия переводчика.

Тем не менее фраза автора:
This is the P2PKH format of public key. It is widely used for sending/receiving bitcoins. There are various rounds of different hashing algorithm involved to generate P2PKH key from hex of public key.
подтверждает мою мысль о том, что у автора терминологическая каша в голове по поводу адресов и открытых ключей.

Вместо слова адрес автор использует несуществующий термин P2PKH key, что вводит в заблуждение читателя.

legendary
Activity: 1974
Merit: 4715
A-Bolt, достаточно было написать о неточностях перевода  Smiley
Правки внесены.
legendary
Activity: 2317
Merit: 2318
Ok! Итак, начнем. Биткоин-адрес имеет два компонента: закрытый ключ и открытый ключ. Открытый ключ - это ключ, который предоставляется отправителю и всему миру. Пример: 18J6ai34uzGofUuzbiwhXJzKzdx1efDBqA.

Адрес - это двойной хеш (SHA256 + RIPEMD160) открытого ключа, записанный в кодировке Base58. Нет в составе адреса никаких двух компонентов.

Иллюстрация из книги Mastering Bitcoin Андреаса Антонопулоса:



1. Public Address (Публичный адрес)

Это формат открытого ключа P2PKH. Он широко используется для отправки/получения биткоинов. Существуют различные раунды разных алгоритмов хэширования, задействованных для генерации ключа P2PKH из шестнадцатеричного открытого ключа.

Для отправки биткойнов используется хеш открытого ключа получателя, а не сам открытый ключ. Поэтому тип транзакции называется P2PKH - Pay to Public Key Hash. Что за бред написан про различные раунды разных алгоритмов я вообще не понимаю. Автор смешал в одну кучу адреса и открытые ключи и считает, что так и надо.

Очень жаль, что технически безграмотная статья заслужила столько меритов в англоязычной части форума. Деградация налицо.
legendary
Activity: 1974
Merit: 4715
Тестирование

Code:
console.log(`This is Bitcoin Address: ${publicAddress}
This is WIF Key: ${WIF}
This is Uncompressed Public Key: ${uncompressedKey}
This is compressed Public Key: ${compressedKey}
Thisi is hexadecimal of Private Key: ${privateKey}`);

После записи вышеуказанного кода в файл сохраните файл и откройте терминал. Теперь выполните следующую команду в терминале. Убедитесь, что папка открыта в терминале:

node index.js (имя файла может быть другим у вас)

Вы получите адрес, WIF, открытые ключи, закрытый ключ в терминале, и они будут соответствовать ключам, сгенерированным bitaddress. Вы можете попробовать это на другом закрытом ключе. Просто измените закрытый ключ в шаге 3.

Но давайте не будем забывать, что эта тема предназначена для обучения. Всегда используйте ключи, генерируемые кошельками, такими как Electrum и MyCelium, если вы полностью не уверены, что делаете.

Надеюсь, вам понравилось это руководство. Дайте мне знать, если у вас все еще есть какие-либо сомнения, я отвечу на них.

Для получения полного кода посетите: https://webtricks.website/keyGeneration.html

Посмотреть, как работает код: https://key-generation-by-webby.herokuapp.com/


Это перевод поста пользователя webtricks. Спасибо ему за информацию!
legendary
Activity: 1974
Merit: 4715
Step 4. Создание сжатого и несжатого открытого ключа

Code:
const checkKey = key => key.length < 64 ? '0'.repeat(64 - key.length) : key;

const publicKeyX = checkKey(publicKey[0].toString(16));
const publicKeyY = checkKey(publicKey[1].toString(16));

const uncompressedKey = '04'+publicKeyX+publicKeyY;

let compressedKey;
if (publicKey[1]%BigInt(2)===BigInt(1)) {
  compressedKey = '03'+publicKeyX;
} else {
  compressedKey = '02'+publicKeyX;
}

Бинго! Мы достигли первой цели. Мы создали несжатый и сжатый открытый ключ. В приведенном выше коде мы прежде всего создали функцию checkKey. Эта функция делает интересную вещь. Возможно, что при преобразовании координат X и Y из числа в шестнадцатеричное число результирующая длина X и Y не равна 64. Но, как мы обсуждали ранее, длина несжатого ключа равна 130, где первые два символа равны 04, затем 64 символа X,и затем 64 из Y. Таким образом, мы добавляем нули,для заполнения пустоты, если длина меньше 64. Например, если длина X равна 63 символам, мы добавим один 0, чтобы сделать его 64.

Затем мы определили шестнадцатеричное значение координаты X как publicKeyX, а Y как publicKeyY. Вы можете видеть, что мы используем toString (16) во второй и третьей строке. Этот код преобразует число в шестнадцатеричное, а затем общая checkkey проверяет, является ли длина меньше 64, затем добавляет 0, если нет, возвращает тот же ключ.

Затем мы определили несжатый ключ как uncompressedKey, а затем сжали ключ как 03 + X, если Y нечетный, и 02 + X, если Y четный.


Step 5. Создание P2PKH ключа

Прежде чем начать с кода, давайте обсудим процесс генерации ключа P2PKH. Следует отметить, что несжатый и сжатый ключ, который мы сгенерировали на шаге 4, не был специфичным для биткоинов. Есть несколько других сервисов, таких как Gmail или Facebook, использующих криптографию Elliptic Curve(элептической кривой) для создания открытых/закрытых ключей. Однако именно на этом шаге мы конвертируем наш открытый ключ в специфичный для Биткойн формат, то есть P2PKH. Ниже приводится графическое представление процесса, да художник вернулся : D



Поэтому мы начнем с несжатого ключа, сгенерированного на шаге 4 (мы также можем начать со сжатого ключа, который будет генерировать другой адрес P2PKH, но может использоваться взаимозаменяемо и принадлежит одному и тому же секретному ключу). Затем мы используем sha256 на несжатом ключе. Затем ripemd160 хешируем на предыдущее значение. Затем мы добавляем 00 перед предыдущим хешем. Это наш 21-байтовый двоичный адрес. Сгенерируем следующие 4 байта двоичного адреса. Мы должны выполнить двойное хэширование sha256 для первых 21 байта. Возьмите первые 4 байта результирующего хэша, т.е. первые восемь символов результирующего хэша, и добавьте его в конце 21 байт. Наконец, мы получаем 25-байтовый двоичный адрес, и мы должны преобразовать его в код Base58. Теперь давайте посмотрим на окончательный код.
 
Code:
const keyHex = Buffer.from(uncompressedKey, 'hex');
const ripedHashedKey = ripemd160(sha256(keyHex));
const mainRipeKeyString = '00'+ripedHashedKey.toString('hex');
const mainRipeKey = Buffer.from(mainRipeKeyString, 'hex');
const doubleHashedKey = sha256(sha256(mainRipeKey)).toString('hex');
const checkSum = doubleHashedKey.substr(0, 8);
const binaryAddress = Buffer.from(mainRipeKeyString+checkSum, 'hex');
const publicAddress = bs58(binaryAddress);

Приведенный выше код - не что иное, как те же 8 шагов, которые я подробно описал на рисунке выше. Мы успешно создали наш Биткойн-адрес. Теперь давайте перейдем к последнему шагу и сгенерируем наш закрытый ключ WIF из шестнадцатеричного секретного ключа.

Step 6. Создание WIF из закрытого ключа

Как и в предыдущем подходе, давайте обсудим процесс, прежде чем перейти к коду. Генерация WIF из закрытого ключа на самом деле проще, чем предыдущий шаг. Преобразование необработанного шестнадцатеричного секретного ключа в WIF на самом деле имеет много преимуществ. Во-первых, он меньше и проще, чем необработанный шестнадцатеричный. Во-вторых, у него есть встроенная контрольная сумма для проверки правильности закрытого ключа. Давайте сначала посмотрим на рисунок:



Первый шаг прост: мы берем шестнадцатеричную форму закрытого ключа и добавляем 80 перед ним. Обратите внимание, что все эти дополнения, которые мы вносим в код, на самом деле не являются числами. Это шестнадцатеричные коды, например, здесь 80, если преобразовать в десятичную форму, это 128. Хорошо, затем мы выполняем 2 этапа хеширования sha256. Затем мы берем первые 4 байта результирующего шестнадцатеричного значения и добавляем их в конец расширенного шестнадцатеричного секретного ключа. Наконец мы выполняем хеширование Base58 и в результате получаем ключ WIF.

Code:
const pvtKeyExtended = "80"+privateKey;
const extended = Buffer.from(pvtKeyExtended, 'hex');
const hashedExtended = sha256(sha256(extended)).toString('hex');
const checksum = hashedExtended.substr(0, 8);
const finalHex = pvtKeyExtended+checksum;
const WIF = bs58(Buffer.from(finalHex, 'hex'));

Ничего особенного в коде на этот раз тоже нет. Мы просто выполняем все шесть шагов, как указано на картинке выше. Если у вас есть какие-либо сомнения относительно какого-либо кода, вы можете спросить в теме или в ЛС.

Хорошая работа! Мы наконец завершили процесс и сгенерировали наш Биткоин-адрес вместе с ключом WIF. Отлично, теперь давайте проверим код дальше.
legendary
Activity: 1974
Merit: 4715
Это перевод поста пользователя 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/P2PKH

2. 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 значения для проекта:

Code:
npm init -y
npm i ripemd160 --save
npm i bs58 --save

Нам нужны три функции хеширования в нашем коде, а именно sha256, palemd160 и base58, кроме криптографии на эллиптических кривых. sha256 уже присутствует в собственной криптографической библиотеке nodejs. Мы можем либо кодировать два других самостоятельно, либо просто импортировать их. Для простоты этого руководства мы установили выше пакеты naspullmd160 и bs58 npm и будем использовать их в нашем коде. Я проверил исходный код обоих пакетов, и использовать его в коде совершенно безопасно.

Теперь давайте начнем настоящее веселье. Откройте ваш файл и начните с кода. Код в хронологическом порядке. Код шага 1 будет идти в верхней части файла, а код шага 2 начнется там, где заканчивается код шага 1 и так далее:

Step 1. Создание функций хэширования

Code:
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. Создание функции эллиптической кривой

Code:
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 из открытого ключа из вышеуказанной функции и закрытого ключа

Code:
const privateKey = "6EBD5FAB742ED0734B37C63BD2A3CE8797FE4AC63C9A99781F8BEDDF6307094E";
const publicKey = generateECPoints(privateKey);

На этом шаге мы взяли шестнадцатеричное значение секретного ключа (5-й элемент изображения) и поместили его в функцию generateECPoints, созданную на шаге 2. Это даст нам координаты X и Y открытого ключа, которые будут выглядеть следующим образом:
[26552980488606060638326679080566574626825610331305555186819497546906082384636n, 106820354128014061768597493909158935631153585355120117243602895828064095418195n]

Вы можете заметить n в конце каждой координаты. Это n означает, что мы имеем дело с очень большими числами, известными как большие целые числа в Javascript. Также вы можете заметить, что эти координаты не соответствуют X и Y на изображении выше. Ну, мы сгенерировали числа на данный момент. Мы должны преобразовать их в шестнадцатеричные числа, чтобы получить несжатый ключ и сжатый ключ. Давайте сделаем это на следующем шаге.
Jump to: