Технический › Сеть

Транзакция по байтам

Полный разбор hex транзакции RingCT: каждое поле, каждая функция — на настоящих и случайных данных

Коротко

Транзакция Monero — это три подряд идущих куска байтов: префикс (входы, выходы, extra), база RingCT (комиссия, зашифрованные суммы, обязательства) и prunable (Bulletproofs+, CLSAG, псевдовыходы). Ниже — инструмент, который разбирает любую v2-транзакцию побайтно, считает txid, проверяет баланс и, если дать ключ просмотра, находит «свои» выходы и расшифровывает суммы. Для случайной транзакции он показывает все секреты, чтобы было видно, как каждая функция применяется к числам.

Как читать байты

Сериализация Monero простая — никаких заголовков и имён полей, только значения подряд. Два вида кирпичиков:

  • varint — целое число переменной длины: по 7 бит в байте, старший бит = «дальше есть ещё байт». 0x02 = 2, 0x80 0x01 = 128, 0x80 0xb5 0x18 = 400000.
  • ключ / точка / скаляр — ровно 32 байта, без длины. Массивы точек, длина которых известна из контекста (например, s в CLSAG — по размеру кольца), тоже идут без длины.

Поэтому парсер обязан знать формат заранее: тип RingCT, прочитанный в середине, определяет, как читать всё остальное.

префикс version · unlock_time входы: кольцо (16 смещений) + key image выходы: одноразовый ключ P + view tag extra: R, payment ID… база RingCT type = 6 fee (открыто) суммы (8 Б, шифр) outPk = C prunable Bulletproofs+ CLSAG: s×16, c1, D pseudoOuts C̃ можно выбросить на pruned-узле Keccak Keccak Keccak txid = Keccak(h₁ ‖ h₂ ‖ h₃)

Какие функции где применяются

ПолеКто вычисляетФормулаПодробнее
extra: RотправительR = r·Gstealth-адреса
выход: PотправительP = Hs(8·r·A ‖ i)·G + Bstealth-адреса
view tagотправительKeccak("view_tag" ‖ 8rA ‖ i)[0]view tags
ecdhInfoотправительv ⊕ Keccak("amount" ‖ Hs(8rA ‖ i))[0..8]Педерсен
outPkотправительC = y·G + v·H, y = Hs("commitment_mask" ‖ Hs(8rA ‖ i))Педерсен
кольцокошелёк отправителя16 глобальных индексов, гамма-распределение по возраступриманки
key imageвладелец входаI = x·Hp(P)key image
pseudoOutsотправительC̃ = y'·G + v·H, маски так, что Σ C̃ = Σ C + fee·HRingCT
CLSAGвладелец входакольцо вызовов по P_i и C_i − C̃кольцевые подписи
Bulletproofs+отправительдоказательство 0 ≤ v < 2⁶⁴ для всех выходовrange proofs

Что делает узел, получив эти байты

  1. Разбирает байты; любой лишний или недостающий байт — отказ.
  2. Проверяет, что key images новые и лежат в правильной подгруппе.
  3. По смещениям восстанавливает 16 глобальных индексов и достаёт из базы ключи и обязательства этих выходов.
  4. Проверяет баланс Σ C̃ = Σ outPk + fee·H — это делает и инструмент ниже.
  5. Проверяет Bulletproofs+ и CLSAG каждого входа.

Что делает кошелёк получателя

  1. Достаёт R из extra и считает общий секрет 8·a·R (одна операция на транзакцию).
  2. Для каждого выхода: хеш → сравнивает view tag; не совпало — дальше не считает.
  3. Совпало — сравнивает Hs(8aR ‖ i)·G + B с ключом выхода.
  4. Выход наш — расшифровывает сумму, восстанавливает маску и сверяет y·G + v·H с outPk.

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

Проверено на mainnet

Парсер сверен с JSON-расшифровкой monerod на настоящих транзакциях типов 5 и 6 и на coinbase-транзакции: совпадают все поля и txid. Кнопка «настоящая tx из mainnet» загружает одну из них — сравните txid с любым обозревателем блоков.

А как будет выглядеть транзакция FCMP++?

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

префикс version · unlock_time входы: только key image L (смещений кольца больше нет) выходы: ключ O (Carrot) + view tag 3 байта extra: эфемерный ключ, … база новый тип RingCT fee суммы + C опорный блок (какой корень дерева) prunable Bulletproofs+ на вход: Õ, Ĩ, R, C̃ на вход: SA+L одно membership proof на все входы вместо «16 индексов + CLSAG» на каждый вход — «перерандомизированный кортеж + SA+L» и общее доказательство членства схема по текущей реализации; детали формата могут измениться до хардфорка
Что было в RingCTЧто в FCMP++Функции
16 смещений на входссылка на блок, по дереву которого строится доказательствокорень Curve Tree из всех выходов
key image I = x·Hp(P)тот же L = x·Hp(O)двойная трата ловится как раньше
псевдовыход C̃C̃ = C + r_c·G — часть перерандомизированного кортежаперерандомизация
CLSAG на входSA+L на вход (несколько точек и скаляров)обобщённый Шнорр
—membership proof на всю транзакциюGeneralized Bulletproofs + дивизоры
view tag 1 байт, ECDH на Ed25519view tag 3 байта, X25519, якорь от JanusCarrot

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