[Из песочницы] Как самостоятельно имплементировать (Proof of Existence) за 2 шага

Всем привет! Я работаю в компании, QuantNet, которая проводит конкурсы алгоритмических стратегий. В недавнем времени передо мной встала важная задача — обеспечить гарантии неприкосновенности даты юзеров (это чрезвычайно важно, так как для корректной проверки эффективностей стратегий необходимо использовать данные мировых финансовых рынков в режиме реального времени).

Вот тут-то я и столкнулся с концепцией PoE (Proof of Existence). В интернете об этом написано достаточно, но специфика платформы заставила меня немного поломать голову. Поэтому я и решил написать эту статью и поделиться своим опытом в архитектуре и имплементации PoE. Думаю, особенно актуально это будет для ребят из финтеха.

Свою статью я разделил на 3 основных блока:

  • Что такое PoE и когда это может понадобиться
  • Алгоритм имплементации
  • Решение моего конкретного кейса


Итак, что такое Proof of Existence?


Proof of Existence (дословно, доказательство существования) помогает доказать, что документ, файл или данные были созданы в определенную дату и время. Самый масштабный вариант применения — это регистрация патента. Но из того, что я видел, чаще всего это применяется в областях на стыке финансов и IT.

Пример из моей сферы алготрейдинга: у вас есть алгоритм, который дает около 70% верных прогнозов по акциям на следующие 2 недели. Вы решаете продавать ваши прогнозы другим игрокам рынка. Дело за малым — убедить покупателя, что ваши прогнозы верны и были сделаны до результатов торгов, т.е. гарантировать их реализацию в конкретное время.

Как это можно гарантировать? Правильно, имплементировать PoE.

Алгоритм имплементации PoE


Для начала нужно:

  1. Подготовить файл, который вы хотите запатентовать. (Я делал PDF, но вы можете использовать любой другой контейнер).
  2. Получить хеш данного файла (Я использовал формат sha256).


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

Все. С вводной частью закончили. Теперь переходим к самому интересному.

(Для большей наглядности я создал специальный код (исключительно для демонстрации). Демо-пример реализации сервиса вы можете посмотреть вот тут.)

Давайте более детально посмотрим как работает демо.

Предлагаю разделить реализацию на 2 части:

  1. Подготовка смарт контракта и кошелька эфира.
  2. Генерация транзакции в коде на Node.js и закрытого ключа.


Давайте по порядку:

Часть 1: Подготовка смарт контракта и кошелька Etherium

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

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

Код смарт-контракта:

```
pragma solidity 0.5.9;

/**
 * @title Ownable
 * @dev The Ownable contract has an owner address, and provides basic authorization control
 * functions, this simplifies the implementation of "user permissions".
 */
contract Ownable {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev The Ownable constructor sets the original `owner` of the contract to the sender
     * account.
     */
    constructor () internal {
        _owner = msg.sender;
        emit OwnershipTransferred(address(0), _owner);
    }

    /**
     * @return the address of the owner.
     */
    function owner() public view returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(isOwner());
        _;
    }

    /**
     * @return true if `msg.sender` is the owner of the contract.
     */
    function isOwner() public view returns (bool) {
        return msg.sender == _owner;
    }

    /**
     * @dev Allows the current owner to transfer control of the contract to a newOwner.
     * @param newOwner The address to transfer ownership to.
     */
    function transferOwnership(address newOwner) public onlyOwner {
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers control of the contract to a newOwner.
     * @param newOwner The address to transfer ownership to.
     */
    function _transferOwnership(address newOwner) internal {
        require(newOwner != address(0));
        emit OwnershipTransferred(_owner, newOwner);
        _owner = newOwner;
    }
}

/**
 * @title HashStore
 * @dev The contract store the hashes and returns date by hash
 * Only the owner can add new hashes
 */
contract HashStore is Ownable {
    mapping(bytes32 => uint256) private _hashes;
    event HashAdded(bytes32 hash);

    function addHash(bytes32 rootHash) external onlyOwner {
        require(_hashes[rootHash] == 0, "addHash: this hash was already deployed");

        _hashes[rootHash] = block.timestamp;
        emit HashAdded(rootHash);
    }

    function getHashTimestamp(bytes32 rootHash) external view returns (uint256) {
        return _hashes[rootHash];
    }
}

```


Как вы уже заметили, мы использовали 2 отдельных контракта: Ownable и HashStore.
Контракт HashStore отвечает за хранение хешей и выдачи даты хеша по запросу. Контракт Ownable отвечает за проверку того, что новый хеш был добавлен исключительно владелецем контракта.

Чтобы добавить хэш нужно вызвать метод addHash, передав значение sha256 как аргумент нашего файла. Если хеши внутри контракта совпадут, транзакция будет отклонена. Так фильтруется нежелательное дублирование значений с разными датами. Проверяется это тут:

require(_hashes[rootHash] == 0, "addHash: this hash was already deployed");


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

Итак, с контрактом разобрались, теперь нужно задеплоить его в сеть. Для этого нам потребуется две вещи:

  1. адрес эфира для взаимодействия с блокчейном
  2. некоторое количсетво эфира для вызова методов и развертывание контрактов.


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

Часть 2: Генерация транзакции в Node.js и закрытого ключа

Итак, к этому моменту у нас уже есть кошелек эфира, его секретный ключ, контракт в тестовой сети и хеш данных. Осталось настроить взаимодействие с блокчейном.

Для этого будем использовать библиотеку web3.js и создадим токен для своей ноды. Свой я создавал с помощью сервиса infura.io и выглядит он примерно так:

ropsten.infura.io/v3/YOUR_TOKEN_HERE


Для хеширования используем пакет sha256. Формат данных может быть любым, но в примере мы используем данные в JSON.

Как я решил свой кейс с помощью PoE?


Кроме самого наличия PoE, для меня было важно не перегружать пользователей работой с блокчейном и платой комиссии за транзакцию.Например, вызов метода addHash (bytes32 rootHash) стоит 0.2 finney (0.0002 ETH или $0.06 по курсу июнь 2019).

irjr069r94csnmxhnxmddfmgzjq.png

В день выходило около 30 выводов позиции стратегий, т.е это стоит нам $2.1. Если количество пользователей увеличится в 100 раз, а курс Эфира пойдет вверх, стоимость, естественно, пойдет в верх.

Я решил хранить один хеш в блокчейне в течение суток. Этот хеш будет сгенерироваться из хешей ежедневных выводов стратегий и их внутренних идентификаторов.

ehgnqqunmlbx-soeaz9yopkryfw.png

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

В итоге


Для верификации мы создали специальный шаблон, который доступен в виде Jupyter notebook… В любой момент пользователь может загрузить шаблон, заново сгенерировать все и проверить, что все позиции действительно те, которые были созданы и созранены в прошлом.

```
Как это работает:
1. Шаблон загружает с API вывод стратегий пользователя на конкретную дату
1. Шаблон хеширует этот вывод
2. Шаблон загружает с API идентификатор вывода, идентификаторы и хеши других выводов на нужную дату
3. Шаблон хеширует все вместе и проверяет этот хеш с хешем, который доступен в блокчейне на нужную дату
Если хеши совпали значит все работает корректно. Таким же образам шаблон автоматически проверяет все даты и выводы.

```

© Habrahabr.ru