Перейти к содержанию

Хеширование в Bitcoin: байты и неизменность данных

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

В уроке определим точные исходные байты, вычисление отпечатка и границы выводов из совпадения. Приведённая программа предназначена для будущего локального разбора; здесь её не выполняем и не публикуем придуманный результат вычисления.

От текста к байтам

Возьмём небольшое сообщение выпуска курса:

course=17;edition=1;price_sats=30000

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

В следующем полном файле digest.mjs функция принимает именно байты. Значение digest получается при выполнении, а не подставляется из статьи:

import { createHash } from 'node:crypto';

const bytes = Buffer.from(
  'course=17;edition=1;price_sats=30000', 'utf8'
);
const digest = createHash('sha256').update(bytes).digest('hex');
console.log({ bytes: bytes.length, digest });

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

Теперь заменим edition=1 на edition=2. Изменение всего одного знака создаёт другой вход. Хорошая криптографическая функция не сохраняет очевидную связь между похожими сообщениями и похожими отпечатками. По разнице двух хешей нельзя определить, какое именно поле изменили. Чтобы увидеть правку, нужно сравнить сами документы.

Что гарантирует совпадение

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

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

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

Операция На какой вопрос отвечает
Хеширование Совпадает ли конкретное содержание?
Подпись Разрешён ли этот документ обладателем ключа?
Шифрование Кто способен прочитать содержание?

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

Однозначная сериализация

Предположим, теперь выпуск представлен объектом JSON. Записи {"course":17,"edition":1} и {"edition":1,"course":17} могут иметь одинаковый прикладной смысл, но различаются байтами. Пробелы, переносы строк и порядок свойств влияют на результат хеширования. Следовательно, договор должен определять либо точный распространяемый файл, либо канонический способ сериализации.

Для первого прототипа удобнее хранить исходный файл как неизменный объект. Проверяем именно скачанные байты и не преобразуем их обратно в объект перед вычислением. После успешной проверки разбираем JSON и проверяем схему. Такое разделение позволяет независимо ответить на два вопроса: соответствует ли файл известному отпечатку и допустимы ли его поля для приложения.

Если требуется создавать один отпечаток в нескольких языках программирования, простой JSON.stringify без дополнительного договора недостаточен. Нужно определить порядок свойств, допустимые типы чисел, обработку Unicode и исключение неподдерживаемых значений. Позднее в курсе IPFS мы вернёмся к каноническим байтам; сейчас важно заметить источник неоднозначности до появления подписи.

Хеши внутри Bitcoin

Идентификатор транзакции вычисляется из определённой сериализации протокола, а не из ответа RPC, преобразованного в JSON. После появления SegWit данные свидетеля требуют различать txid и wtxid. Аналогично хеш блока относится к заголовку по установленным правилам; это не отпечаток произвольного текстового отчёта о блоке.

В документации часто встречается двойное SHA-256. Для него первый результат передаётся второму вычислению как 32 байта, а не как строка из 64 печатных символов. Эти операции дают разные результаты. Порядок байтов при отображении некоторых идентификаторов тоже определяется соглашениями Bitcoin. Не стоит переносить привычный формат веб-контрольной суммы на сетевую сериализацию без проверки спецификации.

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

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

Независимый контроль входа

Перед сравнением библиотека может сохранять длину файла и название алгоритма рядом с отпечатком. Длина не заменяет хеш, но помогает обнаружить неверный способ чтения: пустое тело ответа или HTML-страницу ошибки вместо манифеста. Если файл получен по HTTP, проверяем статус и ожидаемый тип, затем рассматриваем сами байты. Успешное декодирование UTF-8 также не является проверкой авторства. Оно лишь позволяет интерпретировать содержимое после установления его связи с доверенным значением.

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

Оглавление серии