Эта страница объясняет машину, стоящую за каждым доказательством в Rand: что она доказывает, как прогон программы превращается в то, что валидатор проверяет за миллисекунды, и откуда берётся приватность. О стоимости доказательства и о том, как кошелёк его строит, см. Proofs. О параметрах и хеш-конструкциях см. Cryptography. О наборе инструкций, под который пишется программа, см. The RISC-V ISA.
Единственное утверждение, которое доказывает каждое доказательство
zkVM доказывает утверждение одного вида, для любой программы, которую исполняет цепочка:
Программа с дайджестом
hc, запущенная сbase_pc, на приватном входе, зафиксированном черезH_IN, и сегменте публичного входа, зафиксированном черезH_PUB, остановилась не позднее чем за2ᵗ − 1тактов, израсходовала не болееGAS_LIMITгаза и записала эти восемь выходных слов.
Экранированный перевод — это такое утверждение об одной фиксированной программе, guest-программе пакета скрытых активов, чей hc закреплён в genesis-файле как hc_bundle. Начиная с chain 17 оно строится дважды на каждый перевод: второе, небольшое доказательство об auth guest (hc_auth в genesis-файле) показывает, что отправитель владеет ключом траты (Proofs); начиная с chain 21 вторым может быть доказательство multisig auth guest (hc_auth_multisig), которое показывает, что достаточное число ключей Dilithium2 из скрытого набора поставили подпись, не раскрывая ни самого набора, ни их числа. Конфиденциальный вызов — то же утверждение о развёрнутой программе. Для всех них существует одна машина, один верификатор и один набор ограничений. Поэтому развёртывание программы — это операция над данными (сохранить её слова и hc), а не криптографическая: нет ни схемы для каждой программы, ни доверенной установки.
Система доказательств — STARK, построенный на Plonky3. Ей нужна только хеш-функция: ни церемонии установки, ни эллиптических кривых, поэтому алгоритму Шора здесь нечего ломать.
Конвейер
secret input ──► run the program ──► execution trace ──► polynomials ──► commit ──► FRI ──► proof
(keys, (RV32IM emulator) (nine tables, (one per (8× code, (80 queries,
notes, paths) one row per step) column) Merkle) 20-bit grinding)
every validator: recompute the challenges from the Merkle roots, open 80 random positions,
check the rules there → ≈ 16 ms
Верхний ряд выполняется один раз на машине отправителя: около 100 секунд на пакет и около 7 секунд на сопутствующее доказательство авторизации. Нижний ряд выполняется на каждом валидаторе, только по публичным полям и самому доказательству.
1. Выполнить программу и записать всё
Машина — 32-разрядный процессор RISC-V: RV32IM без FENCE, инструкций CSR и EBREAK (The RISC-V ISA). Доказывающий сначала прогоняет программу на эмуляторе и записывает по одной строке на каждый такт. Эта запись — трасса выполнения. Любой, кто её держит, может шаг за шагом воспроизвести прогон; остальная часть схемы существует для того, чтобы верификатор мог убедиться в правильности трассы, не располагая ею.
Программа общается с внешним миром через восемь системных вызовов и больше ничем:
| системный вызов | № | что делает |
|---|---|---|
HALT | 0 | останавливает; восемь выходных слов становятся окончательными |
WRITE_OUTPUT | 1 | задаёт одно из восьми публичных выходных слов |
READ_INPUT | 2 | читает слово приватного входа |
POSEIDON2 | 3 | хеширует буфер в памяти на месте |
KECCAK | 4 | одна перестановка Keccak-f[1600] |
SHA256 | 5 | одно сжатие SHA-256 |
READ_PUBLIC | 6 | читает слово сегмента публичного входа |
POSEIDON2_LEN | 7 | POSEIDON2 с длиной сообщения, привязанной к хешу (набор ограничений 7) |
MODQ | 8 | одна операция по модулю q = 8,380,417, простого числа Dilithium, над полиномами с 256 коэффициентами в памяти: NTT, обратное NTT, четырёхчленное скалярное произведение или умножение с вычитанием (набор ограничений 9, chain 21). Добавляет в доказательство необязательную таблицу modq; вызывает его только multisig auth guest, и проверка Dilithium2 с ним стоит 80,572 такта против 880,102 в обычных инструкциях |
Обязательства нот, нуллификаторы и пути Меркла не встроены в машину. Это обычный код guest-программы, который размещает в памяти сообщение с доменной меткой и вызывает POSEIDON2. Этот вызов не дополняет сообщение, поэтому [a] и [a, 0] хешируются одинаково. Поставляемые guest-программы вызывают его только на сообщениях фиксированной длины. Новая программа, хеширующая данные переменной длины, использует POSEIDON2_LEN (The RISC-V ISA).
Прерывания нет
У процессора нет инструкции, которая отказывается завершаться, поэтому программа не может остановиться, когда проверка не прошла. Вместо этого guest-программа пакета фиксирует сбой: каждая непройденная проверка добавляет по ИЛИ единицу в регистр bad, а bad — последнее слово, хешируемое в выходной дайджест:
D = H(64; anchor, nf₀..₃, cm₀..₃, fee, burn_a, burn_r, burn_asset, time, c, bad)
c — обязательство авторизации, которое также публикует auth-доказательство (начиная с chain 17; в дайджесте chain 14–16 c не было).
Цепочка никогда не берёт прообраз D у отправителя. Она восстанавливает прообраз из публичных полей транзакции, сама задаёт bad = 0, хеширует и сравнивает. Мошенник может построить совершенно корректное доказательство прогона, в котором проверка не прошла, но этот прогон выдал H(…, 1), а цепочка ищет H(…, 0). Чтобы их совместить, нужна коллизия хеша.
2. Разбить трассу на девять таблиц
Одна таблица, описывающая весь процессор, была бы расточительно широкой, поэтому трасса разбивается на специализированные таблицы, которые доказываются вместе как один пакет:
| таблица | строк на уровне t | что записывает |
|---|---|---|
program | объявляется, ≥ 2⁷ | слова инструкций программы, декодированные в схеме; фиксирует, что CPU может выбирать на исполнение |
cpu | 2ᵗ | одна строка на такт, перед которыми идут строки, хеширующие программу, приватный вход и публичный сегмент; содержит 35 публичных значений |
memory | объявляется, ≥ 2ᵗ⁺² | регистры и RAM, отсортированные по (пространство, адрес, время) |
alu | 2ᵗ⁺¹ | каждая арифметическая, логическая операция, сдвиг, сравнение, умножение и деление, на байтовых лимбах |
range, nibble | по 256 | фиксированные таблицы поиска: байты, степени двойки, 4-битные AND/OR/XOR |
poseidon2 | 2ᵗ⁺² | хеш-перестановка, одна строка на раунд |
input | объявляется, ≥ 2⁷ | зафиксированные слова приватного входа |
public | объявляется, ≥ 2⁷ | слова публичного сегмента (для пакета — привязка транзакции) |
Ещё две таблицы, keccak и sha256, присутствуют только в доказательстве, программа которого их вызывала, и содержат не менее 2⁷ строк каждая; guest-программа пакета не использует ни одну. Набор ограничений 9 (chain 21) аналогично добавляет третью необязательную таблицу, modq: один блок из 1,024 строк на каждый вызов MODQ, объявляемый в новом поле заголовка modq_log_height и присутствующий только в доказательствах multisig auth guest; таблица cpu получила столбцы для этого системного вызова, из-за чего сдвинулись все ключи верификатора. Нижняя граница в 2⁷ строк для каждой объявляемой таблицы появилась вместе с набором ограничений 7 (раздел 9).
Уровень t — одно из 10, 12, 14, 16, 18 или 20 — задаёт размеры таблиц. Каждый пакет доказывается на уровне 14: таблица CPU из 16,384 строк, из которых guest-программа использует около 12,400. Auth guest выполняет 250 тактов на уровне 10; multisig auth guest — от 85,830 до 251,946 тактов на уровне 18 для одной–трёх подписей, причём все высоты, зависящие от числа подписей, ограничены снизу тремя, так что трата 1-из-N выглядит как трата 3-из-N. Остальное — дополнение, поэтому доказательство раскрывает, какой уровень понадобился программе, но никогда не раскрывает, сколько шагов она сделала внутри него (Tiers).
Таблица memory делает RAM проверяемой. Поскольку она отсортирована по адресу, а затем по времени, правило «чтение возвращает последнее значение, записанное по этому адресу» превращается в правило между двумя соседними строками.
3. Связать таблицы шинами
Как верификатор узнаёт, что сумма, которую использовал CPU, — это сумма, которую вычислил ALU? Через шины. Когда CPU нужно сложение, он отправляет сообщение (op, a, b, c); ALU получает по одному сообщению на каждую вычисляемую им строку. В машине семнадцать шин:
| шина | между |
|---|---|
PROGRAM, PROGRAM_WORD | cpu ← program: выборки инструкций и слова, которые поглощает дайджест программы |
MEMORY | cpu ↔ memory: каждое чтение и запись |
ALU | cpu → alu |
RANGE8, POW2, AND4, OR4, XOR4 | проверки диапазона байтов, сдвиги и побитовые операции по фиксированным таблицам |
POSEIDON2 | cpu → poseidon2: одно сообщение на перестановку |
INPUT_DIGEST, INPUT_READ | cpu ← input: слова, которые поглощает дайджест входа, и чтения программы |
PUBLIC_DIGEST, PUBLIC_READ | cpu ← public: та же пара для публичного сегмента |
KECCAK, SHA256, MODQ | cpu → необязательные хеш-таблицы и (набор ограничений 9) таблица по модулю q |
BLIND | каждая таблица → следующая таблица по фиксированному циклу: по одному случайному значению в первой строке (набор ограничений 7) |
Шины проверяются с помощью аргумента с логарифмической производной (LogUp). После фиксации таблиц выбирается случайное значение α, и каждая таблица ведёт накопительную сумму ±1/(α − m) по сообщениям, которые она отправляет или получает. Суммы сокращаются точно тогда и только тогда, когда каждое отправленное сообщение было получено, то есть мультимножества на обеих сторонах совпадают. Одно подделанное сообщение оставляет остаток, который случайное α выявляет с подавляющей вероятностью. Шина памяти использует близкий аналог, проверку перестановки мультимножеств.
Каждая таблица публикует итоговое значение своей накопительной суммы, её терминал, а верификатор проверяет, что терминалы в сумме дают ноль. До набора ограничений 6 терминал был проверяемой функцией содержимого своей таблицы. Начиная с набора ограничений 7 каждая таблица несёт пять дополнительных столбцов и в своей первой строке отправляет одно случайное значение, а значение соседа получает по шине BLIND. Каждый терминал смещается на равномерно распределённый элемент поля вызовов, а сумма остаётся равной нулю.
Шины дайджеста и чтения намеренно разделены на отдельные пары. LogUp уравновешивает сообщения по отдельности, а не по потребителям, поэтому одна общая шина позволила бы провера (prover) убрать слово из дайджеста, и при этом чтение этого слова всё равно прошло бы успешно.
4. Привязать трассу к внешнему миру
Из трассы выходят тридцать пять публичных значений, все в таблице CPU:
| значения | что это |
|---|---|
| 1 | входной pc |
| 1 | уровень (tier) |
| 8 | выходные слова (для бандла это дайджест D) |
| 8 | hc, дайджест программы, вычисленный внутри трассы из слов таблицы программы |
| 8 | H_IN, дайджест приватного входа, взятый по входу, перед которым стоит свежая 128-битная соль |
| 8 | H_PUB, дайджест публичного сегмента, без соли |
| 1 | GAS_LIMIT, потолок газа, который объявляет провер (набор ограничений 8) |
Поскольку hc вычисляется в схеме, доказательство о другой программе не может сойти за доказательство о гостевой программе бандла. Верификатор никогда не видит ни одного слова инструкции: он сравнивает hc с дайджестом, который ожидает. Поскольку H_IN солёный, он привязывает приватный вход, не раскрывая его, даже тому, кто мог бы его угадать. Поскольку H_PUB без соли, верификатор может пересчитать его из слов, которые у него уже есть. Для бандла эти слова образуют привязку транзакции, blake3("rand-tx-bind-1" ‖ chain_id ‖ tx) по транзакции с обнулёнными байтами доказательства, поэтому доказательство, скопированное на транзакцию с другим получателем, мемо, конвертом или идентификатором цепочки, не проходит проверку. Начиная с цепочки 20 (genesis binding_domain: 1) это blake3("rand-tx-bind-2" ‖ genesis_hash ‖ chain_id ‖ tx), так что доказательство не проходит проверку в другой цепочке, даже при том же идентификаторе цепочки; эти слова являются публичным входом, поэтому в схеме ничего не изменилось. Доказательство аутентификации строится по тем же восьми словам. Доказательство вызова, начиная с цепочки 20, привязано так же: его сегмент образуют публичные слова программы, заданные при развёртывании, за которыми следуют восемь слов привязки вызова (rand-call-bind-2, до цепочки 20 rand-call-bind-1), поэтому доказательство вызова, скопированное под другой пакет комиссий, не проходит проверку.
Газ — это мера веса выполнения в машине, в схеме с набора ограничений 8. Таблица CPU содержит столбец GAS, который начинается с 1 и прибавляет 1 на каждую строку, 3 на строку поглощения Poseidon2, 64 на строку SHA256 и 192 на строку KECCAK. В строке HALT схема доказывает GAS ≤ GAS_LIMIT через четыре лимба разности с проверкой диапазона, которые никогда не раскрываются: лимит публичен, а газ, потраченный выполнением, нет. Верификатор отклоняет, ещё до построения ключа, лимит выше того, что мог бы потребовать заголовок самого доказательства:
gas_max(t, k, s) = (2ᵗ − 1) + 2ᵗ⁻² + 191·(2ᵏ / 32) + 63·(2ˢ / 64)
где k и s — объявленные логарифмы высот таблиц keccak и sha256, причём каждое слагаемое равно нулю для отсутствующей таблицы. Потолок заголовка уровня 10 без хеширования равен 1 279, а уровня 14 — 20 479. Цепочка оценивает вызов по его объявленному лимиту (Комиссии).
5. Превратить правила в полиномиальные тождества
Каждый столбец — это список чисел в поле Goldilocks, целых чисел по модулю p = 2⁶⁴ − 2³² + 1. Список из n чисел — то же самое, что многочлен степени меньше n: тот, который проходит через них, когда они расположены на подгруппе {1, ω, ω², …} поля. В Goldilocks есть такие подгруппы каждого размера, равного степени двойки, вплоть до 2³², поэтому преобразование выполняется быстрым преобразованием Фурье.
Правило машины тогда становится уравнением между многочленами столбцов. «Счётчик команд увеличивается на четыре, если только инструкция не является переходом» схематично становится так:
is_jump(x)·(pc(ωx) − target(x)) + (1 − is_jump(x))·(pc(ωx) − pc(x) − 4) = 0
для каждого x из подгруппы, где pc(ωx) означает «следующая строка». Полный набор таких правил таблицы — это её AIR (алгебраическое промежуточное представление). Правила CPU и ALU достигают степени 8; остальные остаются в пределах от 2 до 4.
Многочлен, равный нулю в каждой точке подгруппы, делится на Z(x) = xⁿ − 1. Провер объединяет все правила со случайными весами в один многочлен C(x) и делит его на Z(x). Если каждое правило выполняется в каждой строке, частное является настоящим многочленом низкой степени. Если одно правило нарушено в одной строке, это не так. Вопрос «Был ли верен каждый шаг выполнения в 16 000 циклов?» превратился в вопрос «Близка ли эта функция к многочлену низкой степени?», и на него можно ответить, посмотрев на несколько случайно выбранных значений.
6. Зафиксировать до того, как вопросы станут известны
Провер должен зафиксировать свои многочлены до того, как увидит случайные вопросы верификатора, иначе он мог бы подогнать под них ответы. Он вычисляет каждый столбец на области, в восемь раз большей таблицы (коэффициент расширения 8, скорость кода 1/8), и публикует корень Меркла по вычисленным значениям. Эта избыточность является кодом Рида — Соломона: многочлен низкой степени определяется любой восьмой частью своих значений, поэтому мошеннику, изменившему даже одну строку трассы, приходится изменить большую часть расширенного кодового слова, чтобы остаться согласованным, и случайные выборочные проверки наткнутся на повреждение.
Деревья Меркла используют Poseidon2 над Goldilocks: ширина 8, S-блок x⁷, 4 полных раунда, 22 частичных раунда и 4 полных раунда, дайджесты из четырёх элементов поля (около 256 бит). Каждый лист хешируется вместе с четырьмя элементами свежей случайности, поэтому корень ничего не раскрывает о значениях под ним. Та же перестановка служит коммитментом ноты и хешем нуллификатора внутри машины. Её 86 раундовых констант заданы буквальной таблицей в исходном коде узла (poseidon2_constants.rs), поэтому никакое обновление библиотеки не может их изменить.
7. Выборочная проверка с помощью FRI
Теперь у верификатора есть корни Меркла. Сначала он связывает частное с трассой: выбирает случайную точку z вне области таблицы, запрашивает значение каждого столбца и значение частного в ней и напрямую проверяет C(z) = Q(z)·Z(z). Остаётся убедиться, что зафиксированные функции действительно являются многочленами низкой степени, и именно это проверяет FRI.
FRI работает через свёртку. Разложим многочлен на чётную и нечётную части, f(x) = fₑ(x²) + x·fₒ(x²). Для случайного β от верификатора f′(y) = fₑ(y) + β·fₒ(y) имеет вдвое меньшую степень на области вдвое меньшего размера. Провер фиксирует f′, получает новое β и сворачивает снова (Rand сворачивает до восьми раз за раунд), пока не останется константа, которую он отправляет открыто. Затем запрос выбирает одну случайную позицию в первом слое и прослеживает её вниз, проверяя на каждом слое, что значения, открытые выше, сворачиваются в значение ниже. Функция, далёкая от многочлена низкой степени, не может сохранить согласованность всех этих обещаний.
Rand выполняет 80 запросов. По доказанной теореме о зазорах близости каждый запрос оставляет мошеннику не более 9/16 шанса, так что 80 запросов дают (9/16)⁸⁰ ≈ 2⁻⁶⁶·⁴. По гипотезе ethSTARK, на которую опираются развёрнутые STARK, каждый запрос оставляет 1/8, что значительно меньше.
8. Убрать диалог
Всё описанное выше выглядело как диалог, но транзакция должна быть одним сообщением, которое любой валидатор может проверить самостоятельно. Преобразование Фиата — Шамира устраняет верификатора: провер вычисляет каждый «случайный» вызов как хеш всего, что зафиксировано к этому моменту, с помощью дуплексной губки Poseidon2. Изменение любого коммитмента меняет все последующие вызовы, поэтому провер не может подбирать коммитменты под вызовы.
Два дополнительных приёма закрывают оставшиеся бреши:
- Расширение поля. Поле из
2⁶⁴элементов слишком мало, чтобы выбирать из него challenge: мошеннику нужно, чтобы challenge попал на одно из немногих «плохих» значений многочлена, и он добивается успеха с вероятностью околоN/2⁶⁴независимо от числа запросов. Поэтому трасса остаётся в Goldilocks, но каждый challenge выбирается из его квадратичного расширения, примерно2¹²⁸элементов. - Grinding. Прежде чем будут выведены позиции запросов, доказывающий должен найти nonce, хеш которого вместе с транскриптом начинается с 20 нулевых бит, то есть порядка миллиона хешей. Честный доказывающий платит эту цену один раз. Мошенник, перебирающий позиции запросов, пока они не минуют его подделку, платит её при каждой попытке. Это единственное доказательство работы во всём Rand, и к консенсусу оно отношения не имеет.
Итого: 66,4 бита от запросов плюс 20 от grinding дают 86 бит доказанной стойкости и около 100 бит в рамках гипотезы, где пределом служит член расширенного поля.
9. Откуда берётся нулевое разглашение
STARK сам по себе не обладает свойством нулевого разглашения: значения, раскрываемые в позициях запросов, являются значениями многочленов трассы, и без дополнительных мер они утекают. Конфигурация Rand добавляет четыре слоя:
- каждый зафиксированный многочлен рандомизируется (hiding FRI в Plonky3 подмешивает четыре случайных кодовых слова), так что 80 раскрытых значений может воспроизвести, с тем же распределением, тот, кто трассу никогда не видел;
- каждый лист Merkle солится, так что корни и пути аутентификации ничего не раскрывают;
- случайность доказывающего берётся заново из операционной системы для каждого доказательства, поэтому два доказательства одного и того же платежа выглядят несвязанными;
- каждая объявленная таблица содержит не менее
2⁷строк, а каждый терминал LogUp маскируется (набор ограничений 7). Доказательство раскрывает каждую таблицу в 80 точках запросов и двух точках вне области, а hiding-обязательство подмешивает столько случайных строк, сколько строк в таблице, поэтому таблица скрыта лишь при наличии в ней не менее 82 строк. 128 — наименьшая степень двойки, удовлетворяющая этому.
Слой 4 — это исправление. Проверка от 2026-09-27 восстановила приватные входы вызовов с 31 или менее входными словами по боевым доказательствам, считала счётчики выборок программы по её короткой таблице программы и отличила реальные входные слоты bundle от фиктивных по немаскированным терминалам. Это доказательства, созданные в chain 15 и ранее. Chain 16 (набор ограничений 7 с guest-программой bundle без ветвлений) закрыла все три проблемы, а chain 18–20 сохраняют исправления.
Гарантия — статистическое нулевое разглашение в модели случайного оракула. Чего доказательство не скрывает, определено точно: его tier, объявленные высоты таблиц и его публичные значения, включая лимит газа. Объявленная высота keccak, sha256 или modq, равная 0, означает, что запуск эту таблицу не использовал; так что заголовок доказательства вызова показывает, хешировал ли вызов, и, выше минимального порога, как часто — с точностью до множителя два. Для bundle публичные значения и поля — это якорь, четыре nullifier, четыре обязательства, комиссия, сжигания, сжигаемый актив, время, обязательство авторизации и привязка транзакции. Актив, который переводится, является приватным входом, поэтому платёж в zUSD и платёж в RAND в сети выглядят одинаково. hc детерминирован, поэтому любой может определить, что доказательство относится к guest-программе bundle (так устроено каждое доказательство, перемещающее стоимость), или, для вызова, какую развёрнутую программу оно выполняло.
10. Что проверяет валидатор
Machine::verify_public(hc, words, proof) выполняет проверки от самой дешёвой к самой дорогой:
- объявленные в доказательстве tier и высоты таблиц ограничены (
check_declared_heights) до того, как по ним что-либо будет размечено, так что искажённый заголовок обходится в несколько сравнений, а не в многосекундное построение ключа; bundle обязан объявлять tier 14 без хеш-таблиц и без таблицыmodq, доказательство авторизации с одним ключом — tier 10, а доказательство авторизации multisig — ровно закреплённую форму tier 18 (таблицаkeccakв2¹⁴строк, таблицаmodqв2¹⁶, таблица памяти в2²⁰); каждое доказательство вызова, invoke и состояния отклоняет таблицуmodqдо построения любого ключа; - публичных значений 35, каждое является каноническим элементом поля, при этом
pv[HC] = hc,pv[TIER]равен tier доказательства, каждое выходное слово меньше2³², аGAS_LIMITне превышаетgas_maxиз заголовка; - пакетный STARK проверяется по ключу верификатора, кэшируемому для каждой формы
(tier, program, input, keccak, sha256, public heights), а не для каждой программы; H_PUBравен дайджестуwords, который для bundle является привязкой транзакции, вычисленной самим узлом.
До всего этого узел проверяет каждое отправленное или распространённое по gossip доказательство на каноничность кодирования: его расписание свёртки FRI должно совпадать с тем, которое строит честный доказывающий для степеней, объявленных в заголовке. Начиная с fullnode v0.6.8 (audit v6, ZKV-4) эта проверка сначала отклоняет любую объявленную степень выше 25 бит (MAX_HONEST_DEGREE_BITS, потолок 2²⁴ таблицы памяти плюс удвоение ради нулевого разглашения, максимум, который несёт принятое доказательство). До введения этой границы заголовок с огромной объявленной степенью заставлял узел заново выводить гигантское расписание свёртки в своём цикле событий, удерживая его так долго, как хотелось отправителю; верификатор всё равно отклонил бы такое доказательство, поэтому граница меняет лишь стоимость отказа. Паника внутри этой проверки теперь отклоняет одну эту транзакцию, а не останавливает узел.
Затем реестр заново вычисляет дайджест bundle D при bad = 0 и сравнивает его с восемью выходными словами доказательства. Начиная с chain 20 он также требует, чтобы GAS_LIMIT доказательства bundle в точности равнялся genesis-значению bundle_gas_limit, 20 479, а доказательства авторизации с одним ключом — 1 279 (в chain 21 для доказательства авторизации multisig — 436 159), так что каждый перевод объявляет одни и те же значения. При прогретом ключе вся проверка занимает около 16 мс.
Параметры и почему они выбраны
Каждое приведённое ниже число является частью консенсуса: его проверяет верификатор, доказательство, созданное с любым другим значением, не проходит проверку, а изменение любого из них — это новый набор ограничений и hard fork (Наборы ограничений). Профиль FRI задаётся genesis-файлом (fri_profile: production в chain 21, как и в chain 20), а не самим доказательством.
| параметр | значение | почему такое значение |
|---|---|---|
| базовое поле | Goldilocks, p = 2⁶⁴ − 2³² + 1 | 64-битное простое число, арифметика которого занимает несколько машинных инструкций, с мультипликативными подгруппами каждого размера, равного степени двойки, вплоть до 2³², так что превращение столбца в многочлен — это быстрое преобразование Фурье. Трасса живёт здесь, потому что это дешёвое поле. |
| поле вызовов | квадратичное расширение F_p² (≈ 2¹²⁸) | Вызовы, выбранные из самого Goldilocks, оставили бы нижнюю границу корректности около N/2⁶⁴, которую не снизит никакое число запросов. Расширение сводит это слагаемое не более чем к 2⁻¹⁰⁰ для трасс до 2²⁵ строк. За большее поле платят только вызовы; трасса — нет. Кубическое расширение (≈ 2¹⁹²) — названный запас на случай, если когда-нибудь потребуется 128 бит. |
| коэффициент раздувания | 8 (скорость кода ρ = 1/8, log_blowup = 3) | Сразу две причины. Таблицы CPU и ALU содержат правила степени 8, а с флагом нулевого разглашения частное имеет степень 9 — это максимум, который выдерживает раздувание 8; правило более высокой степени потребовало бы раздувания 16. Кроме того, способность каждого запроса поймать обманщика зависит от скорости кода: (1 + ρ)/2 = 9/16 на запрос доказано, ρ = 1/8 — по гипотезе. Большее раздувание позволило бы обойтись меньшим числом запросов ценой пропорционально большего расширения низкой степени, которое доказывающему приходится вычислять и держать в памяти. |
| запросы FRI | 80 | Число, при котором доказанная корректность остаётся около 86 бит. Однажды его сократили до 27 (наборы ограничений 3 и 4): это достигало гипотетической цели в 100 бит (3·27 + 20 = 101), но оставляло лишь около 42 доказанных бит; аудит zk от 2026-09-12 (замечание ZM1) вернул 80. Запросы составляют основную часть размера доказательства, поэтому доказательство пакета весит ≈ 1,50 MB. Для доказуемых 100 бит потребовалось бы 97 запросов. |
| grinding | 20 бит, на фазе запросов | Прежде чем выбраны позиции запросов, доказывающий обязан найти nonce, хеш которого начинается с 20 нулевых бит: около миллиона хешей, которые честный доказывающий оплачивает один раз, а обманщик — при каждой повторной попытке подобрать позиции. Это дёшево добавляет 20 бит; больший grinding (34 бита дали бы и доказуемые 100) замедляет каждое честное доказательство. На фазе фиксации grinding нет. |
| арность свёртки | до 8 за раунд (max_log_arity = 3) | Свёртка по восемь вместо двух даёт втрое меньше слоёв FRI, поэтому каждый запрос открывает меньше путей Merkle и доказательство меньше — ценой немного большей работы на слой. |
| финальный многочлен | константа (log_final_poly_len = 0) | FRI сворачивает до конца и отправляет одно значение в открытом виде — простейшая заключительная проверка для верификатора. |
| хеш (в схеме, Merkle, транскрипт) | Poseidon2, ширина 8, S-box x⁷, 4 + 22 + 4 раундов, скорость 4, ёмкость 4 | Хеш, построенный так, чтобы быть дешёвым как арифметика поля, поэтому хеширование внутри доказательства (обязательства, нуллификаторы, пути Merkle, дайджест программы) стоит строк, а не тысяч битовых операций. Показатель S-box не должен иметь общих множителей с p − 1 = 2³²·3·5·17·257·65537, и 7 — наименьший подходящий. Ширина 8 при скорости 4 даёт дайджест из четырёх элементов, около 256 бит. 86 раундовых констант — буквальная таблица в узле (poseidon2_constants.rs). |
| ширина дайджеста | 4 элемента поля (≈ 256 бит) | Достаточно для ролей прообраза (нуллификаторы, обязательства: 128 бит против Grover). Этого недостаточно для 128-битной квантовой стойкости к коллизиям в транскрипте (2²⁵⁶/³ ≈ 2⁸⁵); дизайн белой книги требует 384-битного хеша транскрипта, а действующий блокчейн его не принял. |
| сокрытие | 4 случайных кодовых слова в FRI; 4 элемента соли на лист Merkle; свежая случайность ОС на каждое доказательство | Минимум, при котором 80 открытых позиций и пути Merkle можно смоделировать только по публичным значениям, так что доказательство статистически обладает нулевым разглашением. Доказательства одного и того же платежа, сделанные дважды, выглядят несвязанными. |
| нижняя граница таблицы | 2⁷ строк для каждой объявленной таблицы (набор ограничений 7) | Доказательство открывает 80 точек запросов и 2 точки вне области каждой таблицы, всего 82, а таблица из h строк маскируется h случайными строками. 64 строк слишком мало, а 128 — следующая степень двойки. Число запросов выше 126 потребовало бы 2⁸. |
| терминальная маска | одно значение F_p² на таблицу, на шине BLIND (набор ограничений 7) | Терминал LogUp вычисляется по исходной трассе, поэтому без маски он — несоленое обязательство к своей таблице. Маска — элемент поля расширения, потому что одна координата базового поля ограничила бы сдвиг тонким множеством, против которого можно проверять кандидата в свидетели. |
| веса газа | 1 за строку; 3 за строку поглощения Poseidon2; 64 за строку SHA256; 192 за строку KECCAK (набор ограничений 8) | Каждый вес считает строки, которые строка втягивает в другие таблицы: строка поглощения — одну 32-строчную перестановку, строка SHA256 — 64 строки по 466 столбцов, строка KECCAK — 32 строки по 2 612 столбцов и 100 строк памяти. |
| максимальная степень ограничений | 8 | Задаётся раздуванием выше; степень каждой таблицы закреплена тестом, чтобы новое правило не могло незаметно её превысить. |
| уровни | 10, 12, 14, 16, 18, 20 | Таблицы имеют шесть фиксированных размеров, каждый в четыре раза больше предыдущего, и доказательство раскрывает только свой уровень: грубую «корзину» с дополнением, соответствующую длине прогона, а не число его тактов. Каждый пакет — уровня 14, каждое доказательство авторизации — уровня 10; вызовы ограничены уровнем 14 начиная с fullnode v0.5.6. |
| максимальная трасса | 2²⁴ строк (2²⁵ с битом нулевого разглашения) | Размер, для которого доказано слагаемое поля расширения; более длинная трасса отклоняется, а не доказывается под утверждением о корректности, которое не выполняется. |
| публичные значения | 35 элементов поля | Входной pc, уровень, восемь выходных слов, три восьмисловных дайджеста hc, H_IN, H_PUB и заявленный лимит газа: наименьшее утверждение, которое называет программу, приватный вход, публичный вход и границу работы. До набора ограничений 7 включительно их было 34. |
| предел размера доказательства | 2 MiB по умолчанию; 6 MiB в цепочке 21, 4 MiB в цепочках с 17 по 20 (max_proof_bytes) | 2 MiB выше любого доказательства, которое выдают развёрнутые гостевые программы без таблицы хеширования, и ниже доказательства с keccak (≈ 3,3 MB); цепочки с 17 по 20 задают 4 194 304, чтобы пропускать оба, а цепочка 21 задаёт 6 291 456 — запас под доказательство авторизации с мультиподписью (оценка 3,4 MB и более; ещё не измерено). Цепочки с 13 по 16 задают 8 MiB. Предел проверяется до декодирования доказательства. |
Что дают эти числа вместе: 86 бит доказанной корректности, около 100 по гипотезе ethSTARK (где пределом служит поле расширения, а не запросы) и около 85 бит против квантового противника, что задаётся 256-битным хешем транскрипта. Белая книга сознательно держит обе оценки корректности рядом: каждый развёрнутый FRI-STARK опирается на гипотезу ради последних бит, и читатель, оценивающий развёртывание, должен знать, какое допущение несёт какие биты.
Доказательства о доказательствах
При ≈ 1,50 MB доказательство пакета тяжёлое, рядом с ним идёт доказательство авторизации в 1,37 MB, и каждый узел хранит их все. Построенное средство — рекурсия: доказательство, утверждение которого — «я проверил эти доказательства». Fullnode несёт для этого вторую, меньшую машину — рекурсивную VM, чьё родное слово — элемент Goldilocks, а инструкции — собственные операции верификатора (арифметика поля, Poseidon2, шаги Merkle и FRI). Её агрегирующая программа воспроизводит полную проверку N доказательств пакетов и публикует дайджест их публичных значений, привязанный к цепочке, агрегатору и nonce. Проверка одного доказательства пакета внутри неё стоила 1 968 619 строк при наборе ограничений 6 и 2 044 506 при наборе 7; при наборе 8 или 9 она заново не измерялась. Она реализована и отключена генезисом во всех цепочках до сих пор, включая цепочку 21. Агрегирующая программа проверяет только доказательства пакетов, поэтому в цепочке с раздельной авторизацией доказательство авторизации осталось бы. Её рабочим доказательствам к тому же нужно больше памяти, чем планировалось: по измерениям при наборе ограничений 7, 133 GB и более (Предстоящее: агрегация блоков).
Чего схема не утверждает
- Квантовый запас составляет около 85 бит, а не 128. Квантовый поиск коллизий для
n-битного хеша стоит примерно2ⁿ/³. Дизайн из whitepaper предполагает 384-битный хеш транскрипта; работающая цепочка использует 256-битный дайджест Poseidon2. Его расширение — это новый набор ограничений и хард-форк. - 100 бит — это гипотеза; 86 — доказано. Повышение доказанного значения до 100 потребовало бы 97 запросов, то есть примерно на пятую часть больше данных запросов на одно доказательство.
- Константы Poseidon2 получены из заданного seed-значения, а не взяты из опубликованной инстанциации. Об атаках на них ничего не известно; переход на стандартный набор изменил бы каждый хеш в цепочке.
- Часть утверждений проверяется рядом с доказательством, а не внутри него. Публичное значение
TIERсверяется верификатором с формой доказательства, но не ограничивается схемой (аудит ZL1), а дисциплина taint в гостевой программе bundle подкреплена структурным аргументом, проверочным стендом с ослабленной гостевой программой и мутационным фаззингом, но не верифицирована машинно. - Заголовок доказательства не скрыт. Уровень и высоты двух хеш-таблиц передаются открыто, поэтому доказательство вызова показывает, использовал ли вызов
KECCAKилиSHA256. Опция провера, дополняющая обе таблицы, существует, но ни один кошелёк её не включает: она увеличивает доказательство уровня 10 примерно с 1,3 МБ до 3,6 МБ. - Системный вызов
POSEIDON2не является инъективным на входных данных переменной длины. Он оставлен как есть, потому что от него зависят каждое обязательство, нуллификатор, корень и дайджест программы. Ни одна поставляемая гостевая программа пока не используетPOSEIDON2_LEN. - Доказательство строится медленно и выполняется отправителем либо провером, с которым отправитель работает в паре: около 100 с и 5,7 ГБ на CPU ноутбука для bundle. GPU-ядра впервые запустились на GPU (NVIDIA H100) 2026-09-28 и совпали с эталоном на CPU; кошелёк по-прежнему строит доказательства на CPU.
Где читать дальше
- Раздел whitepaper «The Zero-Knowledge Scheme: How a Rand Proof Works» (
randprotocol.tex) разбирает те же семь составляющих вместе с теоремами о стойкости. - Статья о реализации (
randprotocol_implementation.tex) описывает набор инструкций и системные вызовы, гостевую программу bundle, губку и дайджесты, каждую таблицу и шину, конфигурацию машины и виртуальную машину рекурсии. - Код:
crates/randprotocol-zkvm/src/в fullnode —emulator.rs,tables/,machine.rs,gas.rs,guests.rs,hash.rs,poseidon2_constants.rs.