Три хеша одного сообщения

TON-сообщение передаётся как bag of cells — BoC, и «хешем сообщения» называют три разных хеша в зависимости от того, кто говорит. Мемпул-стрим TONNode доставляет все три для каждого ожидающего сообщения под такими именами:

  • cell_hash — representation hash корневой ячейки. Именно его обозреватели называют хешем сообщения, а после включения сообщения в блок он равен хешу in_msg той транзакции. Он зависит только от ячеек, а не от того, как они сериализованы в байты.
  • norm_hash — нормализованный хеш по TEP-467: хеш того же внешнего входящего сообщения, переписанного в фиксированную форму — ext_in_msg_info$10 src:addr_none dest:<unchanged> import_fee:0 init:nothing body:right$1 ^body. Он не меняется при пересериализации сообщения и при изменении полей src, import_fee или init. Если сообщение уже в этой форме, norm_hash равен cell_hash.
  • sha256 — SHA-256 байтов BoC ровно в том виде, в каком они доставлены. Одни и те же ячейки можно сериализовать несколькими допустимыми способами (с индексом или без, с CRC32C или без), и у каждой сериализации свой SHA-256, тогда как два хеша ячеек не меняются.

Инструмент выводит каждый из них в hex и в base64, потому что API расходятся: обозреватели и протокол liteserver используют hex, ряд HTTP API возвращает base64.

Зачем нужен нормализованный хеш

Внешнее сообщение подписывает кошелёк, а в сеть его передаёт тот, кто ретранслирует. По пути его могут перекодировать, а ретранслятор — добавить или убрать адрес src, import_fee или прикреплённый init, ничего из этого кошелёк не подписывал. Каждое изменение даёт новую корневую ячейку и новый cell_hash, так что нода, отсеивающая дубликаты по хешу ячейки, приняла бы один и тот же подписанный payload несколько раз. TEP-467 фиксирует форму, по которой считается хеш, чтобы все эти варианты сводились к одной сущности, и собственная функция ноды get_ext_in_msg_hash_norm (в validator/impl/external-message.cpp) вычисляет его именно так.

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

Что несёт мемпул-стрим

Каждый кадр msg мемпул-стрима TONNode несёт сам boc, его size, а также cell_hash, norm_hash и sha256 в определённом выше смысле. Отсеивайте дубликаты в своей обработке по cell_hash: одно и то же сообщение может прийти дважды — сначала как early (непроверенное), затем как checked, — и ещё раз после переподключения. sha256 — ключ, по которому нода отсеивает дубликаты полученных байтов, поэтому он считается по доставленным байтам, а не по перекодировке.

Проверено на реальных сообщениях

Реализация здесь строит нормализованную ячейку в точности так, как это делает собственный парсер стрима, а набор тестов репозитория пропускает через неё 72 реальных внешних сообщения mainnet — набор фикстур стрима, для каждого из которых известны cell_hash, norm_hash и sha256, доставленные стримом, — и проверяет совпадение всех трёх. Пример сообщения из документации мемпула — одно из них; вставьте его, чтобы увидеть три значения рядом.