June 13, 2022

Iron Fish | Whitepaper на русском языке

Translated by discord: Vadim Ragilo#0812

Введение

АннотацияIron Fish — это децентрализованный, основанный на доказательстве выполнения работы(Proof-of-work), приватный и общедоступный блокчейн-проект. Iron Fish разрабатывается для обеспечения надёжных гарантий конфиденциальности для каждой транзакции. Подобно тому, как изобретение SSL/TLS в 90-х годах открыло путь к электронной коммерции и принесло пользу большому количеству отраслей, мы считаем, что конфиденциальность является фундаментальным требованием для защиты пользователей и развития отрасли использования криптовалюты.

Мы разрабатываем Iron Fish с целью обеспечить простые в использовании, полностью приватные транзакции, строго следуя протоколу Sapling. Каждая учетная запись оснащена ключом просмотра, который предоставляет возможность владельцу учетной записи право на просмотр данных своего счета.

С помощью протокола Sapling мы бросаем вызов предыдущим моделям установки нод. Iron Fish поддерживает WebRTC и WebSockets, что позволяет всем пользователям установить настоящее P2P-соединение без каких-либо других требований к его настройке. Наша первая версия реализации Iron Fish построена таким образом, что в будущих итерациях она может быть расширена для запуска полного узла непосредственно в браузере. Мы стремимся снизить барьер для входа, чтобы любой человек с компьютером чувствовал себя достаточно комфортно для запуска ноды.

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

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

Популярные криптовалюты, такие как Bitcoin, Ethereum и другие, являются наименее приватным способом осуществления транзакций — их протоколы, в основе своей, работают в рамках концепции полной прозрачности для подтверждения транзакций. Хотя существуют и другие монеты конфиденциальности, все они либо не имеют обещанных гарантий приватности, либо настолько сложны в использовании, что практически не находят реального применения (а зачастую и то, и другое).

Здесь есть огромный неиспользованный потенциал — но только если пользователи будут чувствовать себя действительно защищёнными своей криптовалютой и смогут получить к ней доступ. Подобно тому, как введение SSL/TLS в 90-х годах открыло шлюзы для электронной коммерции (предшественники HTTPS), мы считаем, что доступная монета конфиденциальности приведёт к появлению нового класса индустрии безграничных продуктов.

В этой документации мы расскажем, как работает Iron Fish, и почему мы сделали его именно таким. Хотя мы стараемся объяснить всё как можно подробнее и использовать всю необходимую терминологию, некоторые части потребуют базового понимания эллиптической криптографии.

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

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

  • Сеть. В этом разделе рассматривается базовый сетевой стек, последовательность запуска, типы сообщений и реализация протокола синхронизации сети (gossip-протокола).
  • Хранение. Раздел “Хранение” знакомит читателя с основными структурами данных и моделями для Iron Fish, а также с нашей первой реализацией того, как уровень хранения доступен как в CLI, так и в браузерной реализации полной ноды.
  • Майнинг. В разделе “Майнинг” описывается, как создаются новые блоки, содержащие необходимую случайность для доказательства работы (PoW), а также расчет вознаграждения майнеров.
  • Создание аккаунта. В разделе “Создание аккаунтов” описывается, как создаются аккаунты Iron Fish в соответствии с протоколом Sapling, и описывается использование всех необходимых ключевых компонентов для аккаунта.
  • Создание транзакций. Транзакции Iron Fish также строго следуют протоколу Sapling, и в этом разделе описывается, где и как применяются доказательства нулевого знания, как включаются комиссии за транзакции, а также как сбалансировать и подтвердить любую существующую транзакцию.
  • Проверка и консенсус. Наконец, последний раздел связывает все воедино, описывая правила принятия новых блоков, содержащих транзакции пользователей.

Взаимодействие сетей

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

При построении любой децентрализованной одноранговой системы (P2P) необходимо решить проблему трансляции сетевых адресов (NAT). Большинство наших компьютеров, ноутбуков, планшетов и телефонов находятся за маршрутизаторами и брандмауэрами, что затрудняет прямые соединения между участниками P2P-систем. Именно здесь на помощь приходит сетевой уровень.

В то время как некоторые другие реализации сетевого уровня требуют от пользователя настройки перенаправления портов на маршрутизаторе для обхода NAT, мы разработали нашу реализацию с акцентом на доступность, используя комбинацию WebRTC и WebSockets для нашего транспортного уровня. Они используют множество методов, чтобы помочь нодам установить прямую связь. Другими словами, реализация нод Iron Fish работают прямо из коробки, либо в CLI, либо даже непосредственно в браузере. Это упрощает использование Iron Fish для любого пользователя, независимо от его технических возможностей.

Когда нода запускается впервые, она должна знать хотя бы об одной другой ноде, к которой она может подключиться (так называемая загрузочная нода или бутнода), которая даст информацию о других нодах в сети. Это первоначальное подключение к бутноде происходит через WebSocket, а все последующие подключения к узлу используют WebRTC. В этом разделе будет описано, как именно ноды соединяются друг с другом, образуя сеть для поддержки протокола Iron Fish, начиная с того, как запускается новая нода.

Последовательность запуска

При запуске новой ноды происходит следующее:

  1. Новая нода случайным образом выбирает одну бутноду из предоставленного списка и открывает с ней WebSocket-соединение. Если пользователь хочет подключиться к определенному узлу на этом этапе, он может использовать конфигурационный файл или командную строку для указания предпочтительного узла (узлов) начальной загрузки. Загрузочный узел посылает свою идентификацию новому узлу.
  2. Бутнода передает новой ноде список доступных нод (пиров).
  3. Используя этот список, новая нода решает, с какими нодами в сети соединиться.
  4. Третий шаг повторяется с каждой новой нодой, с которой соединяется новая нода, пока не будет достигнуто максимальное количество соединений (до 50).
  5. Чтобы максимизировать силу нашей сети и предотвратить фрагментацию сети, нода будет отдавать предпочтение подключению тем пирам, к которым в настоящее время подключено относительно небольшое количество других известных пиров.

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

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

Учитывая это, давайте рассмотрим, как ноды общаются друг с другом, передавая сообщения.

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

Типы сообщенийСетевой уровень Iron Fish в настоящее время имеет четыре различных внутренних типа сообщений:

  • Идентификация: сообщение, с помощью которого нода может идентифицировать себя для другой ноды в сети.
  • Сигнал: сообщение, используемое для уведомления о сеансе связи в реальном времени (RTC) между двумя пирами.
  • Список пиров: сообщение, содержащее список пиров, к которым данный узел подключен в данный момент.
  • Не удалось обработать запрос: сообщение об ошибке при возникновении проблемы.

Все сообщения в сети Iron Fish направляются по одному из следующих четырех стилей, в зависимости от различных факторов.

  • Gossip (Сплетни): Эти сообщения достигают каждой ноды в сети. Когда нода получает сообщение-сплетню, она проверяет его и пересылает своим пирам. Это используется для распространения изменений в блокчейне среди всех нод сети. Подробнее об этом мы поговорим в следующем разделе.
  • Fire and Forget (Выстрелить и забыть): Эти сообщения направляются конкретной подключенной ноде (невозможно отправить сообщение ноде, к которой вы не подключены). От пира не ожидается никакого ответа или подтверждения получения. Это полезно, когда вам не нужно убеждаться в том, что данные были получены правильно.
  • Прямой RPC: здесь запрос сообщения отправляется определенному подключенному пиру, и система ожидает ответа. Поток удаленного вызова процедур (RPC) состоит из двух потоков: один для запроса и один для ответа. Он используется в качестве подложки для глобального RPC.
  • Глобальный RPC: Эти сообщения не адресованы конкретному пиру. Нода выбирает пира для отправки сообщения и повторяет попытку (до определенного ограниченного количества) с другим пиром, если не получает ответа или если ответ недействителен. Алгоритм выбора рандомизирован и использует веса, чтобы отдать предпочтение пирам, которые, как известно, с большей вероятностью ответят на зарегистрированный тип сообщения.

Давайте немного углубимся в то, как работает протокол сплетен.

Протокол сплетенПротокол сплетен (Gossip Protocol) в основном используется для трансляции новых блоков и транзакций всем нодам сети Iron Fish. Для наглядности: ноды, соединенные вместе, образуют сеть, а блокчейн — это структура данных, о которой они договариваются.

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

В следующем разделе подробно описано, как в настоящее время реализована трансляция на основе сплетен в Iron Fish.

Базовая реализация

Шаг 1Когда новая нода выходит в сеть и соединяется с пиром, он передает список своих пиров.

Представим, что наша текущая сеть состоит из новой ноды A, соединенной с нодами B и C. Сама C соединена с нодами D и E. Сама D соединена с F и G. Визуально это выглядит так, как показано на рисунке ниже.

Визуализация того, как связаны узлы нашего примера.

Ноды знают только о своих прямых соседях и о соседях своих соседей. Нода A не будет знать о нодах за пределами D и E.

Теперь нода A может решить подключиться к некоторым из пиров и сохранит копию списка пиров каждой ноды.

Шаг 2Когда нода A решит передать новое сообщение, он разошлет сообщение типа Gossip всем своим пирам (в данном примере C и B).

Затем каждая последующая нода будет передавать сообщение другим своим пирам, пока сообщение не получит вся сеть. В этом примере C передает сообщение D и E. Затем D передает сообщение F и G.

ОптимизацияЧтобы уменьшить перегрузку сети, мы реализовали следующие усовершенствования протокола сплетен.

Локальная историяЧтобы избежать бесконечной передачи одного и того же сообщения, каждая нода хранит набор всех сообщений-сплетен, которые она видела. Когда нода получает сообщение типа сплетни, которое уже есть в этом наборе (это означает, что оно уже встречалось ранее), она игнорирует это сообщение. Набор, в котором хранятся эти сообщения типа сплетен, имеет определенный размер, а старые сообщения удаляются в порядке очереди.

Рокировка соседейЧтобы избежать спама дублирующимися сообщениями, мы применили еще два решения:

  • Когда нода A передает сообщение ноде B, нода B не отправляет его обратно ноде A.
  • Когда нода A отправляет сообщение ноде B, нода B (зная, что A уже позаботилась об этом) будет избегать отправки сообщений любой ноде, к которой подключены ноды A и B.

В приведенном ниже примере нода A подключена к B, C, D, E и хранит список соединений с пирами на два уровня вглубь.

Когда нода A передает сообщение, распространение происходит в два этапа:

  1. Нода A передает сообщение нодам B, C, D и E.
  2. Нода B пересылает сообщение в G. Она не пересылает его в C и E, поскольку знает, что нода A подключена к ним и уже отправила его. Нода C пересылает сообщение в H. Нода D пересылает в I, а нода E — в F.

Когда нода F передает сообщение, в нашем примере распространение происходит в четыре этапа:

  1. Нода F передает сообщение ноде E..
  2. Нода E пересылает сообщение нодам A и B.
  3. Нода B пересылает сообщение ноде G. Она не пересылает сообщение ноде A, поскольку знает, что E подключена к A. Она не пересылает сообщение ноде C, поскольку знает, что та подключена к A.
  4. Ноды C и D пересылают сообщение нодам H и I.

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

Хранилище:

Этот раздел мы начнем с рассмотрения того, что мы храним (структуры данных и модели Iron Fish), а затем перейдем к тому, как мы храним их (используя LevelDB и IndexDB). Мы начнем с рассмотрения самых основных структур данных, которые представляют глобальное состояние Iron Fish: банкноты и нуллификаторы.

Структуры данных и модели

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

Содержимое заметки в открытом виде выглядит следующим образом:

  • (pk, d): ключ передачи и диверсификатор адреса получателя (например, владельца открытого ключа записки, о котором мы расскажем в разделе “Счет”);
  • v: значение открытого текста, которое хранится в записке;
  • rcm: случайность записки, используемая для генерации хэша Педерсена для банкноты;
  • memo: 32-байтовое поле для заметок.

НуллификаторЭто гарантирует, что банкнота не может быть потрачена дважды (поскольку для этого пришлось бы дважды раскрыть один и тот же нуллификатор, а такие попытки будут отклонены). Нуллификатор уникален для своей банкноты, поскольку он получен из частной информации о записке: nk(ключ получения нуллификатора, подробнее об этом в разделе "Счет"), cm (обязательство записки) и position(индекс в дереве Меркла).

Деревья МерклаКак упоминалось выше, Iron Fish хранит как банкноты, так и нуллификаторы в деревьях Меркла. Дерево Меркла — это аккумуляторная структура данных, то есть она используется для представления многих элементов одним маленьким идентификатором (хэшем).

Дерево Меркла для банкнотДерево Меркла для банкнот имеет фиксированный размер с глубиной 32; оно используется для хранения всех создаваемых банкнот. В отличие от других блокчейнов, где UTXO удаляется после расходования, это дерево Меркла является структурой данных только на добавление, то есть банкноты добавляются в дерево последовательно. Хотя дерево Меркла глубины 32 очень велико (оно может хранить 4 294 967 296 банкнот!), оно конечное, что является не самым лучшим решением для постоянно растущей валюты. Когда дерево Меркла полностью заполняется, оно становится деревом только для чтения, позволяя тратить из него старые банкноты. Затем с нуля строится новое дерево Меркла.

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

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

Структура Меркла для банкнотыБанкнота состоит из:

  1. value_commitment:
    Обязательство Педерсона для значения банкноты.
  2. note_commitment:
    Обязательство Педерсона со сдвигом, которое используется для хэширования всего содержимого структуры Меркла для банкноты.
  3. public_key:
    Открытый ключ, который создается при создании заметки, с которой он связан. Он используется для того, чтобы получатель мог расшифровать банкноту и использовать ее в будущем, используя технику обмена ключами Диффи-Хеллмана.
  4. encrypted_note:
    Зашифрованная банкнота, которую получатель может расшифровать, используя вышеупомянутый public_key.
  5. note_encryption_keys:
    Зашифрованное поле, в котором хранится вся информация, необходимая отправителю для последующей расшифровки заметки (чтобы отправитель мог восстановить историю транзакций).

Дерево Меркла для нуллификаторовПодобно тому, как дерево Меркла для банкнот является накопителем для банкнот, дерево Меркла нуллификаторов является накопителем для нуллификаторов. Оно просто используется для отслеживания всех нуллификаторов (которые представляют собой 32-байтовые числа), когда-либо раскрытых, когда были потрачены сопровождающие их заметки.

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

У нас не было бы всех этих банкнот и нуллификаторов, если бы они не были частью транзакций (более подробно мы рассматриваем транзакции, которые принимаются в общий блокчейн). Далее мы рассмотрим, как именно все эти компоненты образуют блоки, которые, в свою очередь, составляют блокчейн Iron Fish.

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

Учитывая все это, как заголовок блока помогает подтвердить блок?

Заголовок блокаЗаголовок блока состоит из следующего (некоторые из этих терминов, например, описание вывода, мы рассмотрим далее в статье):

  1. sequence:
    Порядковый номер этого блока. Блоки в цепочке увеличиваются в порядке возрастания последовательности. Более одного блока могут иметь одинаковый порядковый номер (что указывает на форк в цепочке), но за один раз выбирается только один форк.
  2. previousBlockHash:
    Хэш предыдущего блока в цепочке.
  3. noteCommitment:
    Обязательство перед деревом Меркла банкнот после того, как в него будут добавлены все новые заметки от транзакций в этом блоке. Хранится в виде хэша и размера дерева на момент вычисления хэша.
  4. nullifierCommitment:
    Обязательство перед набором нуллификаторов после того, как в него будут добавлены все траты в этом блоке. Хранится в виде хэша и размера набора на момент вычисления хэша.
  5. target:
    Хэш этого блока должен быть меньше целевого значения, чтобы блок был принят в цепочку.
  6. randomness:
    Nonce, используемый для вычисления хэша этого блока.
  7. timestamp:
    Текущее время в формате UNIX по данным майнера, добывшего блок. Это значение следует принимать недоверчиво, но майнеры захотят убедиться, что оно находится на достаточном расстоянии от временной метки предыдущего блока.
  8. minersFee:
    Одна (упрощенная) транзакция, представляющая плату майнеров, состоящая только из одного описания выхода. Обратите внимание, что хотя в заголовке блока отсутствует хэш блока, его можно вычислить с помощью алгоритма хэширования Iron Fish, учитывая все элементы заголовка блока.

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

  1. Предыдущий блок, на который ссылается данный блок, существует (с помощью поля previousBlockHash).
  2. Цель та, с которой соглашается проверяющий узел (подробнее о том, как рассчитывается цель и сложность, позже).
  3. Когда все содержимое заголовка блока хэшируется, этот хэш оказывается численно меньше целевого, это в основном достигается майнером за счет подстройки значения случайности.
  4. Временная метка для этого блока имеет смысл (временная метка больше, чем у предыдущего блока на 12 секунд, +/- 10 секунд в качестве буфера).
  5. Что все транзакции в блоке действительны (подробнее об этом в разделе “Транзакции”).
  6. Что вознаграждение майнеров, которым майнер вознаграждает себя за представление этого блока, является действительным, то есть это именно согласованное вознаграждение за блок плюс все транзакции в транзакциях (подробнее об этом в разделе “Майнинг”).
  7. И, наконец, после добавления всех транзакций в два глобальных дерева Меркл, дерева банкнот и дерева нуллификаторов, соответствующие корни деревьев Меркл обновляются и правильно ссылаются в BlockHeader как noteCommitment для корня Меркл дерева банкнот и nullifierCommitment для корня Меркл дерева нуллификаторов.

Как Iron Fish хранит данныеДо сих пор мы говорили о том, что необходимо хранить ноде Iron Fish, но не о том, как. В этом разделе мы рассмотрим, как именно Iron Fish хранит банкноты, нуллификаторы, блоки, транзакции и заголовки блоков таким образом, чтобы уровень хранения работал как в качестве инструмента интерфейса командной строки (CLI), запускаемого как программа на вашем компьютере, так и полностью в браузере.

Поскольку мы знали, что запустить полную реализацию Iron Fish в браузере будет сложнее, чем в консоли NodeJS, мы в первую очередь сосредоточились на этом. Наиболее надежным выбором базы данных для приложений, которым она нужна в браузере, является IndexedDB. К сожалению, для NodeJS не существовало сопутствующей реализации IndexedDB, поэтому мы выбрали LevelDB для нашей реализации NodeJS.

Чтобы избежать необходимости жонглировать двумя отдельными реализациями хранения данных для двух разных баз данных, наша реализация Iron Fish имеет общий уровень абстракции для хранилищ данных и доступа к базе данных на основе LevelUp. Этот уровень абстракции заботится о конкретных реализациях базовой базы данных и предоставляет общий уровень, который можно использовать как в браузере, так и в среде NodeJS, предлагая простой API, не зависящий от хранилища данных.

API уровня храненияПроще говоря, слой хранения представляет собой API над базовыми хранилищами данных: он может создавать хранилища на основе схем и работать с ними с помощью всех обычных операций для работы с парами ключ-значение, таких как GET, PUT, DEL и HAS. Для полного обзора конкретной реализации нашего слоя хранения данных, пожалуйста, обратитесь к README слоя хранения данных в нашем репозитории Github.

Майнинг

До сих пор мы рассматривали структуру блокчейна Iron Fish; этот раздел будет посвящен тому, как блокчейн Iron Fish расширяется за счет новых блоков. Обратите внимание, что мы используем термины “ноды” и “пиры” как взаимозаменяемые; нода в Iron Fish всегда является пиром. Несколько других кратких определений:

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

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

Чтобы стать майнером, нода должна иметь обе глобальные структуры данных (дерево Меркла для банкнот и дерево Меркла для нуллификаторов) и знать как минимум два последних блока.

Создание блокаКак мы уже говорили, блок состоит из заголовка блока и тела блока. Тело блока — это просто список из 0 или более транзакций; блок, не содержащий транзакций, называется пустым блоком. Процесс создания блока одинаков независимо от того, есть ли в блоке транзакции или нет:

  1. Определение тела блока;
  2. Настройка сложности и цели;
  3. Добавление вознаграждения майнера на основе графика эмиссии монет;
  4. Создание заголовка блока.

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

Блок с недействительной транзакцией будет отклонен другими нодами, поэтому перед включением его в тело блока майнер должен сначала проверить эту транзакцию (подробнее о проверке транзакций в разделе “Создание транзакций”).

Настройка сложности и цели для блока

СложностьЦелевое время для блока Iron Fish в настоящее время равно 60 секундам. Это значение может быть изменено.

Цель сложности майнинга проста. При необходимости она корректируется в каждом блоке, чтобы майнерам было труднее или легче добывать блоки, таким образом, чтобы новые блоки добавлялись в блокчейн каждые 55–65 секунд (в среднем 60 секунд). Если сеть (то есть все ноды сети) не произвела блок в течение более 65 секунд, сложность предстоящего блока снижается (по сравнению со сложностью предыдущего блока). И наоборот, если майнер хочет произвести блок менее чем за 55 секунд, сложность блока с этой временной меткой будет выше, чем у предыдущего блока.

Расчет сложности Iron Fish во многом повторяет расчет сложности Ethereum, описанный в EIP-2, с некоторыми отличиями.

Чтобы определить сложность предстоящего блока, мы сначала вычисляем интервал времени, к которому принадлежит блок. Временной интервал определяется как расстояние (в интервалах по 10 секунд), на котором временная метка блока находится от желаемого диапазона от 55 до 65 секунд после предыдущего блока. Предстоящий блок, который находится через 45–55 секунд после предыдущего блока, будет иметь значение временной метки -1, блок, который находится через 35–45 секунд после предыдущего блока, будет иметь значение временной метки -2, и так далее:

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

В настоящее время Target.minDifficulty() равен 131072, но это может быть изменено.

Целевое значениеМы обсуждали регулировку сложности для обеспечения 55–65 секунд между блоками; для этого мы регулируем цель, которая представляет собой число, под которое должен подпадать хэш блока (например, быть численно меньше цели).

Цель рассчитывается из сложности по следующей формуле:
цель = 2**256 / сложность

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

Добавление вознаграждения майнера на основе графика эмиссии монетВознаграждение майнера (сколько монет выделяется майнеру за успешную добычу блока) привязано к кривой эмиссии Iron Fish. Идея кривой эмиссии Iron Fish заключается в том, что в течение первого года после запуска основной сети (мэйннета) общее количество монет увеличится на четверть от стоимости генезисного блока (за счет вознаграждения за блок). В последующие годы количество новых монет будет становиться все меньше и меньше в соответствии с функцией распада, но никогда полностью не достигнет 0.

Формула для определения количества новых монет, которые будут отчеканены в определенный год после запуска, следующая:

Где s — начальный запас генезисного блока в 42 миллиона монет, k — коэффициент распада -.05, а x — год после запуска сети (начиная с 0).

“Год” Iron Fish в количестве блоков составляет 525 600 блоков в один календарный год (при условии 60-секундного времени блока). Мы используем приведенную выше формулу для расчета вознаграждения за блок, используя “год” Iron Fish, округленный до ближайшего .125 монеты:

Поэтому вознаграждение за блок и общее предложение в течение первых нескольких лет после запуска будут такими:

Кривая эмиссии, использующая вышеупомянутую формулу вознаграждения за блок, при общем предложении в 256 970 400 монет будет выглядеть следующим образом:

Годы после запуска

Чтобы получить вознаграждение за успешную добычу нового блока, майнер создает специальную транзакцию miner fee в заголовке блока. Значение этой транзакции открыто, чтобы другие могли убедиться, что это именно вознаграждение за блок плюс все транзакционные сборы от транзакций, включенных в этот блок. Адрес получателя этой транзакции с вознаграждением майнера остается скрытым. Подробнее о вознаграждении майнера читайте в разделе “Создание транзакции”.

Построение заголовка блокаПосле определения тела блока майнер может построить для него заголовок блока.

Заголовок блока состоит из следующих данных:

  1. sequence:sequence строится с использованием порядкового номера последнего блока и увеличением его на единицу. Это поле указывает на позицию данного блока в блокчейне, начиная с нуля.
  2. previousBlockHash:Поле previousBlockHash заполняется хэшем последнего блока в блокчейне в соответствии с алгоритмом хэширования Iron Fish. Это указывает на то, что новый блок основан на последнем известном блоке в блокчейне.
  3. noteCommitment:Все новые заметки, включенные в тело блока, применяются (по порядку) к дереву Меркла для банкнот. Полученный новый корень Меркла для этого дерева и его размер (в терминах глобального количества заметок) используются для построения noteCommitment для заголовка блока.
  4. nullifierCommitment:Для построения nullifierCommitment для заголовка блока используется тот же процесс, но с раскрытием нуллификаторов.
  5. target:target определяется алгоритмом сложности и цели.
  6. timestamp:timestamp — это время добычи данного блока. Временная метка текущего блока должна быть больше, чем временная метка предыдущего блока, и может спешить максимум на 60 секунд, чтобы снизить эффект для тех, кто проверяет блок в реальном времени.
  7. minersFee:minersFee — это специальная транзакция для начисления вознаграждения за блок на любой адрес по выбору майнера. Стоимость этой транзакции вознаграждения за блок известна и может быть проверена, но адрес получателя скрыт. Для получения более подробной информации смотрите, как создается транзакция вознаграждения за блок (ссылка на раздел “Транзакция вознаграждения майнера” в разделе “Создание транзакций”).
  8. randomness:randomness — это 64-битное число, такое, что при хешировании всего содержимого заголовка блока с помощью алгоритма хеширования Iron Fish результат будет численно меньше заданного.

Алгоритм хэширования Iron FishСпецифика алгоритма хэширования будет объявлена ближе к дате запуска основной сети.

Создание Аккаунта

Теперь, когда мы рассмотрели логистику криптовалюты, ориентированной на конфиденциальность, мы сосредоточимся на основных деталях того, как создаётся кошелёк Iron Fish для осуществления полностью приватных транзакций.

Счета и транзакции в Iron Fish в значительной степени зависят от протокола Sapling, но с некоторыми оговорками. В этом разделе мы разберём все ключевые компоненты аккаунтов — как они создаются, как используются, что необходимо знать их пользователю.

Начнём с ключевых компонентов, которые используются для просмотра баланса, отправки транзакций или просмотра их истории. Все ключевые компоненты аккаунта Iron Fish создаются на основе одного секретного ключа, он же Secret Key. Хотя конструкция для аккаунта может показаться сложной, общий смысл заключается в том, что, помимо секретного ключа, каждый счёт имеет набор ключей для расходования средств, ключи просмотра, которые могут быть предоставлены любому третьему лицу для доступа только лишь к чтению, а также публичный адрес, используемый для получения средств от других лиц.

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

Мы объясним, зачем нужны все эти тонкости, по ходу дела.

Secret key (Секретный ключ)Секретный ключ — это просто 32-байтовое случайное число. Это seed, необходимый для создания всех остальных частей вашего кошелька.

Spending Key Pair (Пара расходных ключей)Эта пара ключей используется для осуществления записей, связанных с аккаунтом, и формируется непосредственно из секретного ключа.

The Spend Authorization Key (ask) — он жеключ авторизации входа, является компонентом закрытого ключа и получается путём хэширования секретного ключа и модификатора с помощью алгоритма хэширования Blake2b (с параметрами персонализации), а затем преобразуется в скаляр для кривой Jubjub.

The Authorization Key (ak) — ключ авторизации, который определяется как открытый ключ для ключа авторизации путём умножения ключа авторизации входа на фиксированную базовую точку генератора для него на кривой Jubjub.

Уравнение для этой пары выглядит следующим образом:

Где ak — ключ авторизации, ask — ключ авторизации входа, и G(ak)

— это базовая точка генератора для него на кривой Jubjub.

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

Секретный ключ (Secret Key) используется для получения ключа авторизации входа (ask), который, в свою очередь, используется для получения ключа авторизации (ak).

Nullifier Key Pair (Пара ключей-нуллификаторов)Эти ключи ответственны за создание нуллификаторов, необходимых для траты средств (или же изменения записи), полученных из секретного ключа.

The Proof Authorization Key (nsk) — ключ авторизации доказательства является компонентом пары ключей-нуллификаторов и создаётся путём хэширования секретного ключа и модификатора с помощью функции хэширования Blake2b (с параметрами) и последующего преобразования в скаляр (он же — целое число) для кривой Jubjub. Ключ авторизации доказательства используется в описании транзакции ввода, доказывая, что выявленный нуллификатор был вычислен правильно. Помните, что для того, чтобы потратить средства, пользователь должен раскрыть свой уникальный нуллификатор как часть транзакции. Доказательство с нулевым разглашением в описании транзакции гарантирует, что раскрытый нуллификатор был создан правильно, то есть с использованием ключа авторизации владельца.

The Nullifier Deriving Key (nk) — производный ключ нуллификатора определяется как открытый ключ для ключа авторизации доказательства, созданного путём умножения ключа авторизации доказательства на фиксированную базовую точку генератора G(nk):

Где nk — ключ выведения нуллификатора, nsk — ключ авторизации доказательства, и G(nk)–это базовая точка генератора для него на кривой Jubjub.

Secret Key используется для получения ключа авторизации доказательства (nsk), который в свою очередь используется для получения ключа нуллификатора (nk).

View Key Pair (Пара ключей просмотра)

The Outgoing View Key (ovk) — ключ исходящего просмотра позволяет расшифровывать исходящие транзакции. Он получается путём хэширования секретного ключа и модификатора с помощью хэш-функции Blake2b с дополнительными параметрами, затем берётся первые 32 байта результата.

The Incoming View Key (ivk) — входящий ключ просмотра позволяет расшифровывать входящие транзакции. Он получается с помощью хэш-функции blake2s для хэширования байтов авторизующего ключа с байтами производного ключа нуллификатора и преобразования его в скаляр Jubjub:

ivk=blake2s(ak, nk), преобразующийся скаляр Jubjub.

Secret Key используется для получения исходящего ключа просмотра. Входящий ключ получается из ключа авторизации (ak) и производного ключа нуллификатора (nk).

Публичный адресПубличный адрес состоит из ключа передачи и диверсификатора. Вместе они позволяют одному кошельку с одним закрытым ключом содержать до 2¹¹ публичных адресов.

ДиверсификаторДиверсификатор (d) — это случайное 11-байтовое число, используемое для рандомизации конечного публичного адреса. Диверсификатор преобразуется в аффинную точку на кривой Jubjub (gd),которая используется для создания ключа передачи — Transmission Key.

Transmission Key (Ключ передачи)The Transmission Key — ключ передачи(pkd) получается путём умножения диверсификатора (преобразованного в аффинную точку на кривой Jubjub, gd) на входящий ключ просмотра:

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

Публичный адрес — это 43-байтовое число (11 байт для диверсификатора + 32 байта для ключа передачи).

Дивергент преобразуется в точку на кривой Jubjub, представленную как (gd), и используется для получения ключа передачи. Соединяясь вместе, диверсификатор и ключ передают публичный адрес.

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

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

Создание транзакций

Как и аккаунты, транзакции в значительной степени зависят от протокола Sapling, но с некоторыми отличиями. Все транзакции Iron Fish являются защищёнными транзакциями, то есть они не раскрывают никакой информации посторонним лицам, не имеющим явного к ним доступа.

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

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

  1. Компоненты транзакции;
  2. The Spend description component. Компонент описания входа (тот, который диктует, как аккаунт может потратить средства);
  3. The Output description component. Компонент описания вывода (тот, который создаёт новые записи);
  4. Как транзакция балансируется, чтобы убедиться, что соответствующие суммы были потрачены и выплачены;
  5. Как валидатор (например, майнер) может проверить любую транзакцию;
  6. Особый тип транзакции, называемый транзакцией Miner Fee, которая используется для вознаграждения майнера за успешную добычу блока;
  7. Как шифруются и расшифровываются записи, чтобы только соответствующие стороны могли просматривать детали транзакции.

Компоненты транзакцииТранзакция — это просто список описаний входов и выходов.

  • В описании входов указываются записи, которые были использованы в транзакции.
  • В описании выходов создаются новые записи, появляющиеся в результате транзакции, включая сдачу отправителю, если в записи больше ценности, чем нужно получателю.

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

Например, если у Алисы есть средства достоинством в пять монет, и она хочет отправить Бобу четыре монеты, то транзакция будет выглядеть следующим образом:

  • В ней будет одно описание входа для тех средств, которые она тратит (в данном примере это пять монет), а также уникальный нуллификатор для этой записи.
  • И в ней также будет два описания выхода: одно для Боба — в размере четырёх монет, одно для себя — в качестве сдачи в размере одной монеты, так как средства, использованные в описании расхода, не могут быть потрачены снова.

Для обеспечения конфиденциальности описание Spend description тратит средства, не раскрывая, какая именно сумма была потрачена, с помощью доказательства с нулевым разглашением (в частности, zk-SNARK Groth16 Sapling proof). Описание Output аналогичным образом создаёт зашифрованную запись с помощью доказательства с нулевым разглашением о том, что вновь созданная записка была создана правильно. Построение схем для этих доказательств взято из примитивных гаджетов Sapling, которые, в свою очередь, были построены с помощью инструмента построения схем bellman.

Хотя объяснение zk-SNARKs выходит за рамки данной статьи, для читателей, желающих узнать больше, построение zk-SNARK можно разбить на следующие 5 шагов:

  1. Вычисление;
  2. Арифметическая схема;
  3. R1CS (система ограничений первого ранга);
  4. QAP (квадратичная арифметическая программа);
  5. SNARK.

Для изучения шагов 1–4 мы рекомендуем этот туториал Стефана Демила из Decentriq, а для изучения шага 5 подойдёт этот отличный туториал Максима Петкуса.

Структура транзакции Iron Fish состоит из следующих частей:

  • Плата за транзакцию: комиссия (в виде открытого текста), которую получит любой майнер, успешно включивший эту транзакцию в блок.
  • Расходы: список описаний расходов.
  • Выходы: список описаний выходов.
  • Обязательная подпись: Обязательная подпись, которая одновременно подписывает транзакцию и используется для проверки ее баланса — это означает, что она не уничтожила и не создала деньги из воздуха, и что действительно все средства в описаниях расходов за вычетом средств в выводе. описания, равные комиссии за транзакцию. Подписанное здесь сообщение представляет собой хэш транзакции, который является хэшем blake2b сериализованной комиссии за транзакцию, описаний расходов и выходных описаний.

Помните, что вознаграждение майнера за добычу блока также является типом транзакции. Далее в этом разделе мы рассмотрим транзакцию вознаграждения майнеров и рассмотрим все эти концепции транзакций более подробно.

Описание расходовОписание расходов — это часть транзакции, в которой тратятся заметки, связанные с учетной записью. Цель описания Spend состоит в том, чтобы тратить банкноты, не раскрывая, какие банкноты были фактически потрачены, с помощью доказательств с нулевым разглашением (в частности, доказательств типа Groth16 zk-SNARK).

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

  1. он пытается потратить записку, которую тратящий может расшифровать
  2. эта заметка существует в Дереве заметок Меркла
  3. обязательство значения (см) для этой заметки было построено с использованием истинного значения этой заметки
  4. обнаруженный нуллификатор является уникальным нуллификатором для этой заметки и был построен правильно
  5. подпись сопоставляется с ключом авторизации отправителя

Все это достигается за счет наличия общедоступных данных, необходимых для проверки подтверждения и балансировки транзакции (подробнее об этом в разделе «Балансировка транзакции»), а также доказательства.

Структура описания Spend выглядит следующим образом:

CV — это ценностное обязательство (как обязательство Педерсена) для заметки. Он вычисляется при построении описания расходов следующим образом:

Где v — стоимость банкноты, G(v) — это точка генератора, используемая для значения, rcv — это случайность для дальнейшего скрытия хэша обязательства значения, а G(rcv)— точка генератора, используемая для случайности.

rt — это корневая привязка, указывающая, какой корень Меркла использовался для построения доказательства с нулевым разглашением. Доказательство подтвердит, что в дереве существует заметка с указанным корнем Merkle. Однако работа майнера заключается в том, чтобы убедиться, что этот корень Merkle связан с действительным деревом.

nf — это нуллификатор, и он уникален для заметки. Конструкция нуллификатора проверена в доказательстве, но еще раз задача майнера состоит в том, чтобы убедиться, что этот нуллификатор не был обнаружен в прошлом. Нульификатор вычисляется с использованием хэш-функции blake2s, обязательства банкноты(cm), положения потраченной банкноты в дереве Меркла и ключа получения нульификатора(nk):

Где | обозначает создание одного массива байтов для хранения обоих элементов вместе.

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

Где rsk — закрытый ключ рандомизированного открытого ключа, ask — закрытый ключ ключа авторизации, а α — случайность

Sig — это подпись, которая подписывает хэш транзакции транзакции, в которой содержится описание расходов, с использованием рандомизированного ключа (rk).

И, наконец, у нас есть доказательство, которое представляет собой доказательство роста 16 zk-SNARK, проверяющее при нулевом знании достоверность всего описания расходов.

Как генерируется доказательство?Частными параметрами, которые используются для создания доказательства (и не раскрываются впоследствии), являются:

Путь merkle — это путь Merkle от заданного корня (rt, корневой якорь) до расходуемой заметки (в частности, ее обязательства по заметке) с использованием хэшей Педерсена. Доказательство проверяет, что путь является действительным и правильным, и что данная позиция является правильной позицией для привязки заметки в дереве Merkle на самом низком уровне (вы можете думать о позиции как о записи в индексе).

Где g(d)является диверсификатором (преобразованным в аффинную точку на кривой Jubjub) отправителя, а pk(d) являющийся ключом передачи отправителя. Доказательство проверяет, что g(d) не малого порядка и что pk(d) был правильно рассчитан.

Помните, что pk(d) = g(d) * ivk(входящий ключ просмотра). Несмотря на то, что входящий ключ представления не передается сюда напрямую, у нас есть все необходимое для его пересчета, поскольку ivk получается путем хеширования (с использованием хэш-функции blake2s) ключа авторизации (ak) и ключа получения нуллификатора (nk) вместе с некоторые параметры. Здесь у нас также нет непосредственного ключа получения нуллификатора (nk), но мы можем получить его, используя переданный в проверке ключ авторизации (nsk), поскольку nk = G(proofGenerationKey) * nsk.

Также помните, что обязательство по стоимости вычисляется как
cv = v * G(v) + rcv * G(rcv) и поэтому мы передаем значение (v) и случайность для значения (rcv) в доказательство, чтобы проверить конструкцию обязательства ценности. Обязательство примечания (см) является обязательством Педерсена (в результате которого ставится полный балл) содержания примечания (значение (v), g(d), pk(d)) и случайность, используемая для фиксации примечания (rcm) cm = pedersenHash(v, g(d), pk(d)) + rcm * G(noteCommitmentRandomness)

Альфа α вместе с ключом авторизации ak используется для построения рандомизированного открытого ключа, который используется для подписи описания расходов.Здесь, в доказательстве, мы проверяем, что рандомизированный ключ был создан правильно, проверив, что:

Наконец, мы проверяем, что нуллификатор вычислен правильно. Доказательство сначала проверяет, что
nk = nsk * G(proofGenerationKey), а затем проверяет, что нуллификатор действительно был вычислен как:

Таким образом, доказательство проверяет:

  1. Запись Приверженности Целостности
  • Убедитесь, что обязательство по примечанию (cm) получено из g(d), pk(d), value, и rcm

2. Действительность пути Меркла

  • Проверьте, что путь Merkle действителен от данного merkle root до листа(что эта заметка существует в данном дереве)

3. Ценностные обязательства

  • Убедитесь, что значение обязательства (cv) действительно было построено как cv = v * G(v) + rcv * G(rcv)​

4. Нуллификатор

  • Убедитесь, что нуллификатор является производным от nk (ключа получения нуллификатора владельца), cm (обратите внимание на обязательство) и позиции

5. Случайный Ключ Авторизации

  • Убедитесь, что случайный ключ авторизации (rk), который используется для подписи транзакции, правильно получен из ключа авторизации расходов (ak) и альфа (α) для случайности.

Как проверяется доказательство?

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

Публичные переменные, необходимые для проверки подтверждения описания расходов, — это большинство других полей описания расходов:

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

Описание вывода

Выходное описание — это часть транзакции, которая создает новые заметки. Создаваемая им заметка шифруется таким образом, что только владелец входящего ключа просмотра для получателя и владелец исходящего ключа просмотра для отправителя может расшифровать заметку. Он также содержит доказательство с нулевым разглашением (также доказательство Groth16 zk-SNARK), которое подтверждает, что вновь созданная заметка была создана правильно с правильным значением.

Структура описания вывода содержит следующие поля:

Ниже представлено более глубокое погружение в эти поля.

Cv — это обязательство по стоимости (как обязательство Педерсена) создаваемой заметки, вычисляемое как:

Где rcv — это случайность для фиксации значения и частный параметр доказательства с нулевым разглашением для проверки этого вычисления.

cm — это обязательство по заметке (как обязательство Педерсена) для новой создаваемой заметки, которая добавляется в дерево заметок Меркла майнером, который обрабатывает транзакцию, содержащую это описание вывода. Он рассчитывается как:

Где rcm — это случайность фиксации ноты, используемая в этом вычислении фиксации Педерсена и проверенная в доказательстве с нулевым разглашением.

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

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

C(out) из себя представляет собой зашифрованный блок данных, облегчающий держателю исходящего ключа отправителя расшифровку зашифрованной заметки.

И, наконец, у нас есть доказательство (доказательство Groth16 zk-SNARK) для Исходящего описания, которое сверяет все эти общедоступные параметры с приватными, которые использовались для их создания. Дополнительные сведения о ключах и заметках, обсуждавшихся выше, см. в разделе «Шифрование и расшифровка заметок».

Как создается и проверяется доказательство?

Доказательство исходящего описания намного проще, чем доказательство описания расходов. Многие термины, которые вы здесь увидите, будут вам знакомы с теми, которые вы видели в генерации подтверждения описания расходов.

Частные параметры, которые используются для создания доказательства:

И общедоступные параметры, которые используются для проверки доказательства:

Доказательство подтверждает, что:

  1. g(d) для получателя имеет немалый порядок и что эфемерный открытый ключ вычислялся как:

2. Что обязательство по стоимости (см) правильно рассчитано как обязательство Педерсена:

Добавление примечания дерева Меркла из исходящего описания

Выходные данные в описании вывода сохраняются как примечание дерева Меркла, а также являются частью транзакции. Примечание дерева Меркла состоит из следующих полей, взятых из описания вывода:

Балансировка транзакций

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

Балансировка транзакции происходит с помощью обязательств по стоимости в описаниях расходов и вывода и подписи привязки в транзакции. Помните, что структура сделки Iron Fish состоит из следующих частей:

  • Плата за транзакцию: комиссия, которую получит любой майнер, успешно включивший эту транзакцию в блок.
  • Расходы: список описаний расходов
  • Выходы: список описаний выходов
  • Подпись привязки: подпись привязки, которая одновременно подписывает транзакцию и используется для проверки ее баланса.

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

Транзакция криптографически подписывается ключом проверки привязки (bvk), что приводит к подписи привязки в транзакции. Ключ проверки привязки создается путем добавления всей случайности (например, значений rcv) из обязательств входных значений и вычитания всей случайности из обязательств выходных значений. Становится более понятно, почему эта подпись необходима для балансировки транзакции, если мы сначала попробуем сбалансировать ее без нее.

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

Правило, которому мы должны следовать, заключается в том, что inputvaluesoutputvalues=transactionfee. Поскольку точки генератора (например, G(v) и G(rcv) ​одинаковы для всех обязательств по стоимости, мы можем безопасно сложить все обязательства по стоимости из входных данных и вычесть их из выходных данных и упростить. Помните, что, поскольку эти операции выполняются на эллиптической кривой, мы не будем выполнять операции деления, так как это было бы логарифмически сложно. Вместо этого мы умножим комиссию за транзакцию на ту же точку генератора, что и значения (G_v), чтобы проверить равенство.

Мы знаем, что сумма всех входных значений за вычетом суммы всех выходных значений должна составлять комиссию за транзакцию, а это означает, что:

Осталась еще вторая часть уравнения, касающаяся случайности, которая не была учтена:

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

Для этого примера:

Транзакция не раскрывает ключ проверки привязки (bvk) напрямую (транзакция содержит только криптографическую подпись транзакции, подписанную ключом подписи привязки), однако ее можно получить из общедоступной информации.

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

что эквивалентно:

Поскольку действительная транзакция будет иметь G(v) (v1 + v2 — v3 — v4) = G(v)(transaction_fee), валидатор вычисляет ключ проверки привязки как:

Если действительно все значения входных описаний за вычетом всех значений выходных описаний равны комиссии за транзакцию, то bvkbvk должно равняться оставшейся случайности:

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

Это все, что необходимо для проверки баланса транзакции, так как bsk, использованный для подписи хэша транзакции, должен был представлять собой сумму всех случайностей входных описаний за вычетом суммы всех случайностей выходных описаний (в этом примере bsk должен иметь было rcv1 + rcv2 — rcv3 — rcv4), поскольку это единственное правильное решение (называемое открытием) этого общего уравнения, которое все еще находится в формате обязательства Педерсена. Никакого другого решения или открытия быть не может, поскольку у обязательств Педерсена есть свойство, заключающееся в том, что для каждого обязательства может быть только одно открытие.

Проверка транзакции

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

В целом, валидатор должен выполнить ряд проверок для подтверждения транзакции:

  1. Проверьте все доказательства с нулевым разглашением по общедоступным параметрам из описания расходов.
  2. Проверьте все доказательства с нулевым разглашением по общедоступным параметрам из описания вывода.
  3. Убедитесь, что транзакция балансируется
  4. Убедитесь, что каждая подпись в описании Spend подписала хэш транзакции.
  5. Убедитесь, что корневые привязки (rtrt) во всех транзакциях Spend действительны после корней дерева Merkle в дереве Merkle валидатора.
  6. Убедитесь, что ни один из аннуляторов в описаниях расходов не был обнаружен в прошлом.

Транзакция вознаграждения майнера

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

Транзакция Miner Reward очень похожа на обычную транзакцию, за исключением того, что она хранится в заголовке блока (а не в теле блока), не содержит описания расходов и имеет отрицательную комиссию за транзакцию. Эта отрицательная комиссия за транзакцию содержит установленную сумму, выделенную майнеру для добычи блока, а также сумму всех комиссий за транзакцию в теле блока. В этой транзакции может существовать любое количество выходных описаний с выходными значениями, составляющими выделенную сумму. Помните, что комиссия за транзакцию указана в открытом тексте, и любой валидатор может проверить, что
inputvaluesoutputvalues=transactionfee.

Допустим, майнер награждает себя пятью монетами за добычу блока, тогда транзакция Miner Reward будет выглядеть так:

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

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

Примечание Шифрование и дешифрование

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

Помните, что открытый текст заметки (np) состоит из:

  • (pk(d), d)): ключ передачи и диверсификатор адреса получателя
  • v: значение открытого текста, которое содержит заметка
  • rcm: случайность заметки, используемая для создания обязательства Педерсена для заметки
  • memo: 32-байтовое мемо-поле

В выходном описании эта заметка хранится в зашифрованном виде C(enc).

Примечание Шифрование отправителемОтправитель должен знать открытый ключ получателя, который представляет собой комбинацию ключа передачи и диверсификатора (d, pk(d))С помощью этой информации кошелек отправителя может создать sharedSecret, с помощью которого можно зашифровать заметку, чтобы входящий ключ просмотра получателя мог ее расшифровать. Давайте рассмотрим, как кошелек отправителя создает этот общий секрет.

  1. Кошелек отправителя генерирует случайное число и использует его для создания эфемерного секретного ключа (esk) путем преобразования этого числа в скаляр на кривой Jubjub.
  2. Затем он создает эфемерный открытый ключ (epk), используя скалярное умножение между диверсификатором получателя, представленным в виде точки поля, и esk. Этот эфемерный открытый ключ является общеизвестным компонентом описания вывода и виден всем.
  • i. epk=eskg(d)
  • ii. Примечание: g(d) является диверсификатором, d, представленным в виде поля точки на кривой Джубджуба, поэтому мы можем использовать его для скалярного умножения (умножения на эллиптической кривой).

3. Затем он получает sharedSecret, используя обмен ключами Диффи Хеллмана между esk и pkd (диверсифицированный публичный адрес получателя):

  • i. sharedSecret=eskpkd

4. Затем заметка шифруется с использованием sharedSecret и формы симметричного шифрования (в частности, алгоритма симметричного шифрования ChaCha20Poly1305).

Примечание Расшифровка получателемЗатем кошелек получателя может расшифровать зашифрованную заметку в Исходящем описании, используя входящий ключ просмотра получателя. Помните, что ключ передачи получателя (pk(d) получается из диверсификатора (преобразуется в точку на кривой Джубджуб как g(d)) и входящий ключ просмотра: pk(d​)=g(d)​∗ivk

Кошелек получателя может вычислить общий секрет, используя epk (эфемерный открытый ключ), указанный в Исключительном описании: sharedSecret=epkivk

Это тот же sharedSecret, который использовался кошельком отправителя. Обратите внимание, что:

Кошелек получателя рассчитывает:

Кошелек отправителя рассчитывает:

Теперь кошелек получателя может использовать тот же алгоритм симметричного шифрования (ChaCha20Poly1305) для использования sharedSecret и расшифровки C(enc) поле в описании вывода.

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

Помните, что изначально кошелек отправителя мог зашифровать открытый текст заметки (используя алгоритм симметричного шифрования ChaCha20Poly1305) в C(enc) путем вычисления общего секрета как: sharedSecret=eskpk(d)​.

Поскольку у кошелька отправителя нет доступа ни к esk, ни к (pkd) после отправки транзакции эта информация сохраняется во втором зашифрованном поле Исходящего из описания: C(out). Это поле создается отправителем транзакции во время ее совершения и сохраняется в описании вывода.

C(out) поле представляет собой шифрование esk и pk(d), объединенных вместе, также с использованием симметричного алгоритма шифрования ChaCha20Poly1305. Симметричный ключ, используемый для C(out) рассчитывается как:

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

Описание выхода:

В любой момент времени владелец исходящего ключа просмотра (например, кошелек) может воссоздать симметричный ключ шифрования(symmetricEncryptionKey) для расшифровки C(out) поля для извлечения (esk, pk(d)). Затем с помощью (esk, pk(d))кошелек может воссоздать sharedSecret = esk * pk(d) ​и расшифровать C(enc) поле, чтобы окончательно получить открытый текст.

Верификация и консенсус

В предыдущих разделах объяснялось, как создается сеть и как ноды строят новые блоки, но не объяснялось, почему блоки выстраиваются определённым образом. Консенсус — это уровень верификации Iron Fish, который устанавливает правила, согласно которым ноды принимают входящие блоки. Эти правила неявно заставляют их конструировать блок в соответствии с этими правилами, поскольку в противном случае он не будет принят другими нодами сети.

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

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

Напомним, что заголовок блока Iron Fish состоит из следующих полей данных:

  • sequence
  • previousBlockHash
  • noteCommitment
  • nullifierCommitment
  • target
  • timestamp
  • minersFee
  • randomness

Правильный заголовок блока должен соответствовать всем этим правилам проверки:

Последовательность sequence должна быть на единицу больше, чем последовательность в предыдущем блоке (так называемый номер блока), а предыдущий блок должен быть тем, который имеет previousBlockHash. previousBlockHash должен соответствовать действительно существующему блоку.

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

Цель target проверяется с помощью временной метки timestamp этого блока, временной метки предыдущего блока и цели предыдущего блока с помощью формулы цели.

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

MinersFee должен соответствовать формату транзакции Miner Reward, распределяющей майнеру именно вознаграждение за блок плюс все транзакционные сборы за транзакции в этом блоке.

Наконец, случайность randomness допустима, если хэш всего содержимого заголовка блока (включая случайность) даёт хэш, который численно меньше целевого значения, указанного для данного блока.

Проверка тела блока

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

Следующие шаги

Эта первая итерация Iron Fish закладывает основу для частных транзакций, которые может надёжно использовать любой желающий, учитывая низкий порог вхождения. Мы гордимся тем, что мы создали, и с нетерпением ждём следующих итераций, которые будут включать в себя:

  • Пользовательские активы;
  • Функциональные возможности для поддержки стейблкоинов и L2;
  • Широкая поддержка стационарных и мобильных устройств.

Наша миссия — обеспечить действительно свободный ценностный поток, веря, что у каждого из нас есть право на частную жизнь. Если вы хотите узнать больше или присоединиться к проекту, свяжитесь с нами: [email protected].

Приложение

BLS12–381 и кривая Jubjub

BLS12–381 является парной эллиптической кривой с внутренней кривой Эдвардса(вида: −x(2)+y(2)=1+dx(2)y(2), именуемой Jubjub). Параметры для кривой BLS12–381 взяты из Bowe2017. Jubjub была разработана таким образом, что её базовое поле имело тот же размер, что и группа BLS12–381, что позволяет эффективно работать с кривой внутри BLS12–381 zk-SNARK. Параметры для кривой Jubjub взяты из спецификаций Sapling.

Почему Proof of Work?

Мы провели тщательное и масштабное исследование достоинств и недостатков систем Proof of Work, Proof of Stake и Delegated Proof of Stake. В конечном итоге мы пришли к выводу, что системы Proof of Work в целом более безопасны, а также имеют более изученные векторы возможных путей атак, имеют больше возможностей к становлению более равноправной и справедливой децентрализованной системой. Системы Proof of Stake иногда могут привести к лучшему пользовательскому интерфейсу/UX с детерминированной окончательностью и иногда более быстрым временем блока, но плюсы не перевешивают минусы, если рассматривать алгоритмы на основе Proof of Stake в целом.

Продолжить чтение о преимуществах конфиденциальности

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

Оригинал Whitepaper Iron Fish:
https://ironfish.network/docs/whitepaper/1_introduction

Translated by discord: Vadim Ragilo#0812