Криптография в #bitcoin: как создать или сгенерировать адрес Bitcoin на языке C

in #bitcoin2 years ago (edited)

Private key and public key.png
Криптография в #bitcoin: как создать или сгенерировать адрес Bitcoin на языке C

Криптография с открытым ключом является неотъемлемой частью сети Bitcoin. Далее мы объясним, как можно сгенерировать закрытые и открытые ключи для биткойнов, а также адреса для биткойнов с нуля, используя язык программирования C. Мы также расскажем о некоторых вариантах дизайна и функциях протокола #Bitcoin, которые реализовал Сатоши Накамото.

Криптография с открытым ключом (асимметричная)

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

Например, вы можете заменить букву A на B, B на C и так далее. Чтобы расшифровать ваше секретное сообщение, нужно сделать обратное, используя тот же ключ.

Фундаментальный недостаток этой схемы заключается в том, что мы должны поделиться секретным ключом, чтобы использовать его, но поскольку нам еще предстоит установить безопасное зашифрованное соединение для связи, то злоумышленник может перехватить наш секретный ключ!

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

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

В протоколе Bitcoin адрес — это адрес, на который отправляются монеты #bitcoin. Вы можете сгенерировать адрес, взяв хэш вашего открытого ключа. Криптографическая хеш-функция принимает любые входные данные и возвращает строку фиксированной длины. Причём, невозможно вычислить обратное значение, поскольку эти функции являются однонаправленными. Например, часто используемая хэш-функция SHA-256:

SHA256("Vitalij")
= "98746c3152e93a59706e48567e067bb08ea0afe76d05ac27aa9c1ea52340c8ba"

SHA256("Vitali")
= "a03303ed8f20414dd8eeb501f66e5f61cba9dbdf45f96a0d31de5bf63dde43ae"

Попробовать работу хэш-функции SHA-256 можно на этом сайте:
https://emn178.github.io/online-tools/sha256.html

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

Биткойн-адреса (устаревший формат P2PKH)

Биткойн-адреса (устаревший формат P2PKH)

В Bitcoin теперь есть несколько типов адресов (см. /wiki/Address ).

Первоначальные "унаследованные" адреса, которые реализовал Сатоши Накамото, начинаются с номера 1:
1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa.

Этот тип адреса известен как Pay-to-PubkeyHash или сокращенно P2PKH.

Вот шаги, необходимые для расчета устаревшего адреса:

1.. Взять число из 64-х цифр в 16-й системе счисления

public_key = 7aec57a3f9b216a40ff7da9ee24064a837bf61b80e50f6e7fc67ea06bf8a7cc9

Можно использовать вот этот онлайн генератор случайных чисел в 16-ричной системе счисления:
https://www.browserling.com/tools/random-hex

2.. Вычислите хэш SHA-256 открытого ключа

SHA256(public_key) = 2f002458f9c50a2a38e7fe29b213af89cccc05ffb6835c9fce19151a03cc9419

3.. Вычислите хэш RIPEMD-160 этого хэша SHA-256

RIPEMD160(SHA256(public_key)) = 719a1c17453fe2fc18da1f46d3c409e04d3e692a

Можно использовать вот этот онлайн калькулятор для вычисления хэш-функции RIPEMD-160:
https://md5calc.com/hash/ripemd160

4.. Добавьте байт версии адреса перед этим хэшем, чтобы завершить наш адрес ( 0x00 для основной сети)

00719a1c17453fe2fc18da1f46d3c409e04d3e692a

5.. Чтобы создать контрольную сумму, два раза берем хэш SHA-256 полученного в пункте 4 расширенного хэша RIPEMD-160 и берем первые 4 байта или 8 символов в 16-ричной системе счисления

SHA256(SHA256(0x00 & RIPEMD160(SHA256(public_key)))) = e97526ff27a104ccdae771c702dc024010a41fd513042294c99a619ff4203e30

e97526ff - наша контрольная сумма

6.. Добавляем контрольную сумму в конец исходного хэша RIPEMD160 на шаге 4.

00719a1c17453fe2fc18da1f46d3c409e04d3e692ae97526ff

7.. Преобразуем результат из шестнадцатеричной системы счисления в base58 строку

1BMfyQhYFdx2YtAfJFDbmTAgNiy6RbzXxA

Можно использовать вот этот онлайн калькулятор для преобразования числа из 16-ричной системы счисления (hex) в base58:
https://appdevtools.com/base58-encoder-decoder

8.. Удалите все лишние начальные единицы.

В base58 "1" представляет собой нулевое значение и, следовательно, не учитывается перед адресом. Однако, первый лидирующий символ "1" включен для первого '00' байта.

В нашем примере ничего не нужно делать, так как наш адрес выше имеет только одну 1, соответствующую единственной 0x00.

1BMfyQhYFdx2YtAfJFDbmTAgNiy6RbzXxA

Это сложная схема!
Но она включает в себя несколько очень полезных функций, которые подробно описаны ниже.

blockchain cryptocurrency bitcoin.jpg

Контрольная сумма

Контрольная сумма позволяет любому подтвердить, что этот публичный адрес является правильным адресом. Если кто-то пришлет нам адрес, то мы можем проверить его правильность, выполнив шаги 5 и 6 для первых 21 байта (42 символа в 16-ричной системе счисления), а затем проверить правильность последних 4 байтов (8 символов в 16-ричной системе счисления) адреса.

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

База 58

Адрес 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa в формате Base58 на первый взгляд может показаться странным способом представления адресов, но у него есть некоторые качества, которые делают его лучше по сравнению с десятичным представлением 0-9.
Во-первых, использование системы счисления с большим основанием позволяет более короткое представление больших целых чисел.

Например, число 1228 в двоичном формате будет выглядеть так: 10011001100 в двоичном формате (основание 2, двоичная система счисления), но выглядит как NB в base58 (основание 58, 58-ричная система счисления). Поскольку биткойн-адреса технически представляют собой очень большие целые числа, это значительно упрощает отправку и чтение адресов.

Вы можете потренироваться с этим тут:
https://learnmeabitcoin.com/technical/base58

Человек, склонный к вычислениям, может спросить, а почему бы не использовать что-то более естественное, например, base64?
Итак, base58 использует все числа, строчные и прописные буквы, за исключением символов, которые легко ошибиться (0, O, I, l).
Примечание 2 × 26 + 10 − 4 = 58
Удаление этих сбивающих с толку символов значительно снижает вероятность неправильного ввода биткойн-адресов.

Создание биткойн-адресов в C

Создание биткойн-адресов в C

Биткойн использует криптографию на основе эллиптических кривых для создания пары открытого и закрытого ключей. Сейчас всё, что нам нужно знать, это то, что #Bitcoin использует эллиптическую кривую, которая называется secp256k1, для создания открытого ключа из закрытого ключа. И эти ключи обладают свойствами асимметричной криптографии, которые описаны ранее.

Чтобы использовать эту эллиптическую кривую в C, мы будем использовать библиотеку bitcoin-core/secp256k1. Установка этой библиотеки в Linux и MacOS довольна проста и тривиальна.

Чтобы использовать эту библиотеку, мы должны настроить sep256k1_context вот так:

#include <secp256k1.h>

static secp256k1_context *ctx = NULL;

ctx = secp256k1_context_create(
            SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);

Из документации библиотеки:

"Цель структур контекста — кэшировать большие предварительно вычисленные таблицы данных, которые дорого создавать…"

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

Генерация закрытого ключа на языке программирования C

Закрытый ключ в bitcoin — это 256-битное число.
Его можно представить как 32 байта.

В шестнадцатеричном виде оно будет содержать 64 символа:
5A7234743777217A25432A462D4A614E645267556B58703273357638782F413F

Вы также можете поэкспериментировать с генерацией случайного 256-битного числа на этом сайте:
https://www.allkeysgenerator.com/Random/Security-Encryption-Key-Generator.aspx

В системах Unix мы можем получить случайное число из /dev/urandom, если вы сделаете вывод /dev/urandom, то вы увидите целую кучу случайных символов.

cat /dev/urandom

Используя язык C, мы можем загрузить 32 случайных байта, прочитав их из /dev/urandom:

#include <stdio.h>

#include "secp256k1-0.3.0/include/secp256k1.h"
#include "secp256k1-0.3.0/include/secp256k1_ecdh.h"

static secp256k1_context *ctx = NULL;

int main() {
    ctx = secp256k1_context_create(
    SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);
    
    /* Declare the private variable as a 32 byte unsigned char */
    /* Объявляем приватную переменную размером в 32-байта с типом символ без знака */
    unsigned char seckey[32];

    /* Load private key (seckey) from random bytes */
    /* Загрузим закрытый ключ (seckey) из случайных байтов */
    FILE *finput = fopen("/dev/urandom", "r");

    /* Read 32 bytes from finput */
    /* Читаем 32 байта из finput */
    fread(seckey, 32, 1, finput);

    /* Close the file */
    /* Закрываем этот файл */
    fclose(finput);

    /* Loop through and print each byte of the private key, */
     /* Перебираем в цикле и печатаем каждый байт закрытого ключа, */
    printf("Private Key: ");
    for(int i = 0; i < 32; i++) {
            printf("%02X", seckey[i]);
    }
    printf("\n");
}

Комментарий:
"%x" — это спецификатор формата, который форматирует и выводит шестнадцатеричное значение.
Если вы выводите переменную типа int или long, то оно преобразует его в шестнадцатеричный формат.

"%02x" означает, что если выводимое вами значение меньше двух цифр, то "0" будет добавлен в начале.

Сохраним этот код в файл "privkey.c" и скомпилируем с помощью:

gcc -O2 -lgmp -lcrypto -lsecp256k1 secp256k1-0.3.0/.libs/libsecp256k1.a  privkey.c -o privkey

Далее запустим:

./privkey

Программа выведет:

Private Key: DBC6711A5153E1E3532EDE594C2F4B25863414E35FB23396F8F08535DC851ABC

Из-за ограничений кривой secp256k1 не все закрытые ключи будут действительны.
Есть 1/2128 вероятность того, что закрытый ключ недействителен, поэтому нам нужно проверить, действителен ли наш ключ, включив следующий код в нашу программу:

if (!secp256k1_ec_seckey_verify(ctx, seckey)) {
    printf("Invalid secret key\n");
    return 1;
}
// Compile with:
// gcc -O2 -I secp256k1-0.3.0/src/ -I secp256k1-0.3.0/ -lgmp -lcrypto -lsecp256k1  privkey.c

#include <stdio.h>

#include "secp256k1-0.3.0/include/secp256k1.h"
#include "secp256k1-0.3.0/include/secp256k1_ecdh.h"

static secp256k1_context *ctx = NULL;

int main() {
    ctx = secp256k1_context_create(
    SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);
    
    /* Declare the private variable as a 32 byte unsigned char */
    /* Объявляем приватную переменную размером в 32-байта с типом символ без знака */
    unsigned char seckey[32];

    /* Load private key (seckey) from random bytes */
    /* Загрузим закрытый ключ (seckey) из случайных байтов */
    FILE *finput = fopen("/dev/urandom", "r");

    /* Read 32 bytes from finput */
    /* Читаем 32 байта из finput */
    fread(seckey, 32, 1, finput);

    /* Close the file */
    /* Закрываем этот файл */
    fclose(finput);

    if (!secp256k1_ec_seckey_verify(ctx, seckey)) {
        printf("Invalid secret key\n");
    return 1;
    }
    
    /* Loop through and print each byte of the private key, */
    /* Перебираем в цикле и печатаем каждый байт закрытого ключа, */
    printf("Private Key: ");
    for(int i = 0; i < 32; i++) {
        printf("%02X", seckey[i]);
    }
    printf("\n");
    
    
    return 0;
}

Создание открытого ключа на языке C

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

Во-первых, нам нужно объявить новую переменную secp256k1_pubkey для хранения нашего открытого ключа:

secp256k1_pubkey pubkey;

Затем создаем публичный ключ с помощью функции из библиотеки secp256k1:

secp256k1_ec_pubkey_create(ctx, &pubkey, seckey);

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

Теперь нам нужно сериализовать этот адрес, чтобы перевести его в байты. Мы используем переменную size_t для хранения длины адреса в байтах:

size_t pk_len = 65;
char pk_bytes[65];

/* Serialize Public Key */
/* Сериализация открытого ключа */
secp256k1_ec_pubkey_serialize(
    ctx,
    pk_bytes,
    &pk_len,
    &pubkey,
    SECP256K1_EC_UNCOMPRESSED
    );

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

Создание биткойн-адреса на языке программирования C

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

Мы хотим следовать схеме, описанной ранее, для создания биткойн-адреса.
Здесь нам нужно будет использовать библиотеку C openssl (-lcrypto) для обеспечения необходимых функций хеширования SHA256 и RIPEMD160.

В верхней части нашего файла .c мы подключаем файлы этих библиотек:

#include <openssl/sha.h>
#include <openssl/ripemd.h>

Чтобы использовать эти хеш-функции, нам нужны несколько новых переменных:
переменная char длиной 34 байта для хранения нашего адреса,
байтовая переменная rmd длины 5+RIPEMD160_DIGEST_LENGTH (5+20) для хранения наших окончательных хэшей.

Мы также будем использовать переменную байтового типа s для хранения некоторых временных хэшей:

char pubaddress[34];
byte s[65];
byte rmd[5 + RIPEMD160_DIGEST_LENGTH];

Нам нужно определить тип byte в верхней части нашего файла с помощью typedef unsigned char byte.

Во-первых, давайте дублируем наш открытый ключ в переменную s, перебирая каждый байт.

Копируем открытый ключ размером 65 байт:

int j;
for (j = 0; j < 65; j++) {
    s[j] = pk_bytes[j];
}

Следуя схеме, описанной в шагах нашего алгоритма по созданию устаревшего биткойн-адреса.
Мы берем первый хэш SHA256 нашего открытого ключа s в хэш-функции SHA256(s, 65, 0), а затем берем RIPEMD160 этого хэша в функции RIPEMD160(SHA_HASH, SHA256_DIGEST_LENGTH, md), где md — это место, где мы сохраняем выходные данные.

Нам также нужно установить байт версии в начале адреса на 0x00:

/* Set 0x00 byte for main net */
/* Устанавливаем 0x00 байт для основной версии биткоин адреса */
rmd[0] = 0;
RIPEMD160(SHA256(s, 65, 0), SHA256_DIGEST_LENGTH, rmd + 1);

На этом этапе мы реализовали шаги 1-3 нашего алгоритма.
Теперь нам нужно найти контрольную сумму.

Чтобы создать контрольную сумму, мы дважды берем SHA256 полученного на предыдущем шаге хеша:

SHA256(SHA256(rmd, 21, 0), SHA256_DIGEST_LENGTH, 0);

Но нам нужны только последние 4 байта контрольной суммы, поэтому мы можем использовать memcpy для копирования этих байтов в конец первых 21 байта rmd:

memcpy(rmd + 21, SHA256(SHA256(rmd, 21, 0), SHA256_DIGEST_LENGTH, 0), 4);

Чтобы использовать функцию memcpy, нам нужно добавить #include <string.h> вверхней части нашего файла.

Теперь мы очень близки к получению пригодного биткойн-адреса. Осталось только преобразовать эти байты в base58.

Мы можем сами написать и использовать эту функцию base58 для преобразования в base58.

Разместите base58() над нашей функцией main():

/* See https://en.wikipedia.org/wiki/Positional_notation#Base_conversion */
char* base58(byte *s, int s_size, char *out, int out_size) {
        static const char *base_chars = "123456789"
                "ABCDEFGHJKLMNPQRSTUVWXYZ"
                "abcdefghijkmnopqrstuvwxyz";

        byte s_cp[s_size];
        memcpy(s_cp, s, s_size);

        int c, i, n;

        out[n = out_size] = 0;
        while (n--) {
                for (c = i = 0; i < s_size; i++) {
                        c = c * 256 + s_cp[i];
                        s_cp[i] = c / 58;
                        c %= 58;
                }
                out[n] = base_chars[c];
        }

        return out;
}

Чтобы сохранить наш адрес, нам нужна переменная типа char длиной 34 символа.

Теперь мы можем создать наш биткойн-адрес с этими последними строками в функции main:

char address[34];
base58(rmd, 25, address, 34);

В финале, нам нужно удалить все лишние лидирующие единицы в начале адреса.

Мы можем найти количество ведущих 1, которые нам нужно удалить, подсчитав количество 1 в начале адреса и вычтя количество байтов 0x00 в начале нашего хэша rmd.
Если нужно удалить цифру, то мы сдвигаем память, которая хранит адрес, на единицу, удаляя первую цифру.
Мы также укорачиваем строку на количество удаленных единиц.

/* Count the number of extra 1s at the beginning of the address */
/* Подсчитываем количество лишних 1 в начале адреса */
int k;
for (k = 1; address[k] == '1'; k++);

/* Count the number of extra leading 0x00 bytes */
/* Подсчитываем количество лишних начальных байтов 0x00 */
int n;
for (n = 1; rmd[n] == 0x00; n++);

/* Remove k-n leading 1's from the address */
/* Удаляем k-n ведущих единиц из адреса */
memmove(address, address + (k - n), 34 - (k - n));
address[34 - (k - n)] = '\0';

printf("Address: %s\n\n", address);

Когда вы скомпилируете программу с помощью
gcc -O2 -I secp256k1-0.3.0/src/ -I secp256k1-0.3.0/ -lgmp -lcrypto -lsecp256k1 privkey.c -o privkey или gcc privkey.c -o privkey -lcrypto -lsecp256k1
и запустите с ./privkey, то надеюсь, что Вы встретите биткойн-адрес, который начинается с цифры 1:

$ gcc -O2 -lgmp -lcrypto -Wno-deprecated-declarations -lsecp256k1 secp256k1-0.3.0/.libs/libsecp256k1.a  privkey.c -o privkey
$ ./privkey
Private Key: 032496D7D32AA1A5E1114D68397BA778F7173D24F64A782ABC0DA6258C7497F9
Address: 18z8sD3FHE684c5ubyFVeBLUkU38BRcPL1

Формат импорта кошелька (WIF — Wallet Import Format)

Отлично, теперь у нас есть адрес, на который мы можем отправить биткойн!
Но как мы их тратим?

Большинство программных кошельков НЕ ПРИНИМАЮТ необработанные закрытые ключи.
Вместо этого принято использовать формат импорта кошелька; что, как и в биткойн-адресах, упрощает копирование и передачу закрытого ключа.

НИКОГДА НЕ ХРАНИТЕ БОЛЬШИЕ КОЛИЧЕСТВА BTC НА САМОСОЗДАВАЕМЫХ КОШЕЛЬКАХ

Чтобы преобразовать ваш закрытый ключ в WIF:

1.. Добавьте байт версию (0x80 для основной версии биткоина) в начало закрытого ключа.
2.. Рассчитайте хэш SHA256
3.. Снова вычислите хэш SHA256
4.. Берем из этого хеша первые 4 байта, это наша контрольная сумма
5.. Добавлием эти 4 байта в конец расширенного закрытого ключа на шаге 1.
6.. Преобразуем в base58 (можно повторно использовать функцию написанную ранее)

Если все пройдет правильно, то ваш закрытый ключ WIF должен начинаться с цифры 5.
Для получения дополнительной информации см. wiki/Wallet_import_format.

Ванильные адреса биткойнов

Расширив приведенный выше код, вы можете быстро генерировать новые биткойн-адреса в надежде случайным образом получить красивый адрес, такой как 1LIKEALPACAx88... или 100000xX...

Код из этого руководства, который готов к компиляции и запуску

// Compile with:
// gcc -O2 -lgmp -lcrypto -Wno-deprecated-declarations -lsecp256k1 secp256k1-0.3.0/.libs/libsecp256k1.a  privkey.c -o privkey

#include <stdio.h>

#include "secp256k1-0.3.0/include/secp256k1.h"
#include "secp256k1-0.3.0/include/secp256k1_ecdh.h"

#include <openssl/sha.h>
#include <openssl/ripemd.h>

#include <string.h>

typedef unsigned char byte;


/* See https://en.wikipedia.org/wiki/Positional_notation#Base_conversion */
char* base58(byte *s, int s_size, char *out, int out_size) {
    static const char *base_chars = "123456789"
        "ABCDEFGHJKLMNPQRSTUVWXYZ"
        "abcdefghijkmnopqrstuvwxyz";

    byte s_cp[s_size];
    memcpy(s_cp, s, s_size);

    int c, i, n;

    out[n = out_size] = 0;
    while (n--) {
        for (c = i = 0; i < s_size; i++) {
            c = c * 256 + s_cp[i];
            s_cp[i] = c / 58;
            c %= 58;
        }
        out[n] = base_chars[c];
    }

    return out;
}


static secp256k1_context *ctx = NULL;

int main() {
    ctx = secp256k1_context_create(
    SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);
    
    /* Declare the private variable as a 32 byte unsigned char */
    /* Объявляем приватную переменную размером в 32-байта с типом символ без знака */
    unsigned char seckey[32];

    /* Load private key (seckey) from random bytes */
    /* Загрузим закрытый ключ (seckey) из случайных байтов */
    FILE *finput = fopen("/dev/urandom", "r");

    /* Read 32 bytes from finput */
    /* Читаем 32 байта из finput */
    fread(seckey, 32, 1, finput);

    /* Close the file */
    /* Закрываем этот файл */
    fclose(finput);

    if (!secp256k1_ec_seckey_verify(ctx, seckey)) {
        printf("Invalid secret key\n");
    return 1;
    }
    
    /* Loop through and print each byte of the private key, */
    /* Перебираем в цикле и печатаем каждый байт закрытого ключа, */
    printf("Private Key: ");
    for(int i = 0; i < 32; i++) {
        printf("%02X", seckey[i]);
    }
    printf("\n");
    
    
    secp256k1_pubkey pubkey;
    secp256k1_ec_pubkey_create(ctx, &pubkey, seckey);
    
    size_t pk_len = 65;
    char pk_bytes[65];

    /* Serialize Public Key */
    /* Сериализация открытого ключа */
    secp256k1_ec_pubkey_serialize(
        ctx,
        pk_bytes,
        &pk_len,
        &pubkey,
        SECP256K1_EC_UNCOMPRESSED
        );
    
    
    char pubaddress[34];
    byte s[65];
    byte rmd[5 + RIPEMD160_DIGEST_LENGTH];
    
    int j;
    for (j = 0; j < 65; j++) {
        s[j] = pk_bytes[j];
    }
    
    /* Set 0x00 byte for main net */
    /* Устанавливаем 0x00 байт для основной версии биткоин адреса */
    rmd[0] = 0;
    RIPEMD160(SHA256(s, 65, 0), SHA256_DIGEST_LENGTH, rmd + 1);
    
    SHA256(SHA256(rmd, 21, 0), SHA256_DIGEST_LENGTH, 0);
    
    memcpy(rmd + 21, SHA256(SHA256(rmd, 21, 0), SHA256_DIGEST_LENGTH, 0), 4);
    
    char address[34];
    base58(rmd, 25, address, 34);
    
    /* Count the number of extra 1s at the beginning of the address */
    /* Подсчитываем количество лишних 1 в начале адреса */
    int k;
    for (k = 1; address[k] == '1'; k++);

    /* Count the number of extra leading 0x00 bytes */
    /* Подсчитываем количество лишних начальных байтов 0x00 */
    int n;
    for (n = 1; rmd[n] == 0x00; n++);

    /* Remove k-n leading 1's from the address */
    /* Удаляем k-n ведущих единиц из адреса */
    memmove(address, address + (k - n), 34 - (k - n));
    address[34 - (k - n)] = '\0';

    printf("Address: %s\n\n", address);
    
    
    return 0;
}

Протестировать правильность работы нашей программы можно на сайте:
https://www.bitaddress.org/

P.S.:

Если Вы считаете этот пост полезным, то пожалуйста, поддержите меня в создании таких руководств:

USDT (trc20): TTei9TzCvgDsmEoAEdL52w35PALUb4AQ2b
Bitcoin: 3KWuXVHWUwz1BR3zj5nMRWxhAK3YtSMrrU
Litecoin: MLRNskfiRvTGJZPTiMJY8rz5bzRzQmvqD6
Doge: DTLViap4u9F3PZgjKzy5752brQSLJ9TBGg
Dash: XrrxcGZWe16qtrkE9PM7xyJnPcZeWd4RUP