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

Общая структура нашего блокчейна

Итак, наш блокчейн (как и положено) будет представлять собой цепочку из блоков, каждый из которых включает в себя следующее:

  • номер блока [index];
  • метка времени [timestamp];
  • содержание транзакции [transaction];
  • значение так называемого доказательства работы [proof] (о том, что это такое, чуть ниже);
  • значение хеш-суммы предыдущего блока [previous hash];
  • значение хеш-суммы текущего блока [hash].

Какие форматы распределенных баз данных, помимо блокчейна, используются в криптовалютах?

Загрузка ... Загрузка ...

В содержание транзакции мы включим отправителя денежных средств [sender], имя получателя этих средств [recipient] и количество переданных денежных средств [amount]. Для простоты в блок будем включать сведения только об одной транзакции.

Общая структура блока, таким образом, будет выглядеть вот так:

block = {
  'index': 2,
  'timestamp': 1527444458,
  'transactions': [
    {
      'sender': "Petrov",
      'recipient': "Ivanov",
      'amount': 15418,
    }
  ],
  'proof': 4376,
  'previous hash': "000bdf8cd989eb26be64a07b80a8cdcaf27476d8473efbde66c9dd857b94ab9",
  'hash' : "00e86d5fce9d492c8fac40762fa3f4eee9a4ae4a17ee834be69aa05dff1309cc"
}

Помимо очередных текущих блоков, блокчейн должен включать в себя начальный (или первый) блок, с которого, собственно говоря, и начинается вся цепочка блоков. Этот блок называется genesis-блоком, и он, в отличие от текущих блоков, содержит в себе только номер (который всегда равен нулю), метку времени, какое-либо рандомное значение вместо хеш-суммы предыдущего блока (поскольку genesis-блок первый и предыдущего блока у него просто-напросто нет) и собственное значение хеш-суммы:

genesisblock = {
  'index': 0,
  'timestamp': 1527443257,
  'random hash': "f2fcc3da79c77883a11d5904e53b684ded8d6bb4b5bc73370dfe7942c1cd7ebf",
  'hash' : "3fe4364375ef31545fa13aa94ec10abdfdead26307027cf290573a249a209a62"
}

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

Общая схема нашего блокчейна
Общая схема нашего блокчейна
 

Функция подсчета хеш-суммы

Поскольку для подсчета хешей мы собрались использовать алгоритм «Стрибог» (про который мы уже писали), нам необходима соответствующая функция. Ее мы напишем, используя код из указанной статьи. Качаем его отсюда и подключаем в наш проект нужные файлы следующей строчкой:

# include "gost_3411_2012/gost_3411_2012_calc.h"

Саму функцию объявим так:

std::string get_hash_stribog(std::string str)

Далее объявляем структуру для хранения результатов подсчета хешей и выделяем для нее память:

...
TGOSTHashContext *CTX;
CTX = (TGOSTHashContext*)(malloc(sizeof(TGOSTHashContext)));
...

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

...
// Преобразуем строку входных данных к виду const char
const char *c = str.c_str();
// Формируем буфер для входных данных и копируем в него входные данные
uint8_t *in_buffer;
in_buffer = (uint8_t *)malloc(str.size());
memcpy(in_buffer, c, str.size());
...

Далее считаем хеш:

...
GOSTHashInit(CTX, HASH_SIZE);
GOSTHashUpdate(CTX, in_buffer, str.size());
GOSTHashFinal(CTX);
...

Поскольку выход функции тоже должен быть в виде строки, а рассчитанное значение хеша представлено в виде байтового массива, нам необходимо сделать соответствующее преобразование. Сделаем это следующим образом (HASH_SIZE — длина хеш-суммы, 512 или 256 бит, выберем 256):

...
// Формируем буфер для выходных данных
char out_buffer[2 * (HASH_SIZE / 8)];
// Пишем в буфер значение хеш-суммы
for (int i = 0; i < HASH_SIZE / 8; i++)
  sprintf(out_buffer + i * 2, "%02x", CTX->hash[i]);
// Не забываем освободить выделенную память
free(CTX);
free(in_buffer);
// Возвращаем строку с хеш-суммой
return std::string((const char *)out_buffer);

Функция по расчету хешей у нас готова, можно писать дальше.

В чем основной недостаток хеш-функции Streebog?

Загрузка ... Загрузка ...
 

Файл block.h

В этом файле опишем класс CBlock, в который войдет все, что нам нужно для создания блока (как очередного, так и genesis-блока). Однако прежде чем описывать сам класс, определим структуру, которая будет описывать транзакцию. Как мы уже решили, транзакция будет включать в себя три поля — отправитель, получатель и сумма транзакции:

struct STransaction {
  std::string sender;
  std::string recipient;
  uintmax_t amount;
};

Теперь можно приступить к описанию нашего класса CBlock. В него входит public-секция, включающая два конструктора (тот, который без параметров, служит для инициализации genesis-блока, а тот, который с параметрами, — для инициализации очередных блоков); метод, создающий genesis-блок; метод, с помощью которого будет майниться очередной блок; метод, записывающий значение хеша предыдущего блока в нужное место текущего блока; метод получения значения хеша блока из соответствующего поля и private-секция со всеми необходимыми полями (номер блока, имя блока, метка времени и так далее) и одним методом подсчета хеш-суммы:

class CBlock {
  public:
    CBlock();
    CBlock(uint32_t index_in, const std::string &in_name_block, const std::string &in_sender,
      const std::string &in_recipient, uintmax_t in_amount);
    void create_genesis_block();
    void mine_block(uint32_t diff);
    void set_previous_hash(std::string in_previous_hash);
    std::string get_hash();

  private:
    uintmax_t index; // Номер блока
    std::string name_block; // Имя блока
    time_t time_stamp; // Метка времени
    STransaction transaction; // Транзакция
    uintmax_t proof; // Доказательство выполнения работы
    std::string previous_hash; // Хеш предыдущего блока
    std::string hash; // Хеш текущего блока
    std::string calc_hash() const; // Метод подсчета хеша
};

Теперь можно написать реализацию всех указанных методов. Все это мы поместим в файл block.cpp.

Продолжение доступно только участникам

Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее

Вариант 2. Открой один материал

Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.


  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    2 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии