Skip to main content

aihash

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

Правка одного символа ломает проверку

Проверьте сами — две команды

В репозитории лежит настоящий журнал, запечатанный настоящим штампом времени freetsa.org. Не макет: те же файлы прогоняются в тестах.

pip install ./python
aihash verify ./demo/journal
Цепь сходится на всём протяжении.
Запечатано суток: 1 (2026-07-29)
  2026-07-29: rfc3161 — подпись службы штампов проверена (набор корней версии 1, корень freetsa)

Теперь поменяйте один символ: в demo/journal/streams/voice-eu-3/segments/2026-07-29.jsonl найдите возврат придёт в течение 3 дней и исправьте 3 на 5. Та же команда:

НЕ СХОДИТСЯ

  поток voice-eu-3 seq=3: звено не совпадает с записанным

Код возврата 1. Подробности, проверка через браузер и через openssl без нашего кода вообще — в demo/.

Нашли дыру — заводите issue. Формат в FORMAT.md, векторы в spec/, честный список того, что не доказывается, — ниже.


Это не система наблюдаемости. Наблюдаемость отвечает на вопрос «что происходило». Здесь отвечают на вопрос «докажи».

import aihash
log = aihash.open("./journal", realm="romashka-prod", stream="voice-eu-3")

log.record({"actor": "assistant",
            "output.text": "возврат придёт в течение 5 дней",
            "config.model": "gpt-4o-2026-02-11"})

Дальше 364 дня в году ничего не происходит. В день, когда прилетела претензия:

aihash seal    --root ./journal
aihash explain --root ./journal --stream voice-eu-3 --seq 3 --out ep.seal

Оппонент проверяет у себя — офлайн, без обращения к вам, без установки чего-либо:

aihash-verify ep.seal
Вывод: содержимое совпадает с отпечатком и входит в суточное дерево.
Пломба за эти сутки поставлена, но здесь не подтверждена — см. её
строку выше. Неизменность не доказана, пока проверка пломбы не
доведена до конца; отсутствием пломбы это не является.
Запись существовала не ранее 29.07.2026 08:45 UTC (drand:quicknet раунд 30837112)
и не позднее постановки пломбы.

Бинарь проверил всё, кроме подписи службы штампов: разбора CMS в нём пока нет. Подпись сверяет команда SDK — без единого аргумента, потому что корни известных служб вшиты в сам верификатор:

aihash verify ep.seal
  пломба rfc3161: проверена — подпись службы штампов проверена
                  (набор корней версии 1, корень freetsa)

Вывод: содержимое совпадает с отпечатком, отпечаток входит в
суточное дерево, суточная отметка запечатана.
Запись существовала не ранее 29.07.2026 08:45 UTC (drand:quicknet раунд 30837112)
и не позднее Jul 29 08:45:31 2026 GMT (rfc3161).

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

Службы, которой нет в наборе, вердикт не отвергает и не заверяет: «пломба стоит, но здесь не подтверждена», с указанием, какой именно сертификат нужен. Проверить её можно сразу, не дожидаясь нашего выпуска: --tsa-ca <файл.pem> главнее набора.

Разница между двумя выводами и есть продукт: «пломба стоит, подпись не сверена» — это не «пломбы нет», и верификатор обязан говорить именно то, что установил.

Что доказано и что нет

Доказываем Не доказываем
содержимое не менялось после постановки пломбы что записи правдивы
запись существовала не позже времени пломбы что журнал полон
запись создана не раньше времени маяка авторство — без опциональной подписи
записи идут в этом порядке, без вставок и изъятий отсутствие обрезки хвоста между пломбами

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

Как это устроено

У каждого поля записи свой отпечаток, считаемый из имени, соли и значения. Листья собираются в дерево — его корень и есть отпечаток записи. Записи связаны в цепь, цепь за сутки сворачивается в дерево, и на суточный корень ставится пломба в трёх независимых местах сразу:

Пломба Для кого Кто принуждает
RFC 3161 (eIDAS) суд, страховщик служба штампов времени
OpenTimestamps технический скептик цепь блоков Bitcoin
публичная лента обнаружение раздвоения журнала публичность

Отказы этих трёх не коррелируют. Плюс публичный маяк drand в начале каждого отрезка даёт нижнюю границу времени: значение раунда нельзя было знать заранее.

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

Состав

Каталог Что там
FORMAT.md нормативная спецификация формата aihash/1
spec/ тестовые векторы и порядок портирования на новый язык
python/ SDK записи, командная строка, приёмники сохранности, экспортер OpenTelemetry
typescript/ SDK записи для Node, без зависимостей
verifier/ три независимых верификатора: бинарь на Go, страница для браузера, командная строка на Node
PLAN.md принятые решения и причины
demo/ журнал с настоящим штампом, готовый пакет доказательства и верификатор — можно потрогать руками

Продолжаете работу над проектом? Начните с STATE.md — состояние, что проверено, открытые вопросы и первое действие. Потом PLAN.md: там записано, какие решения уже отвергнуты и почему.

Проверка

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

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

pip install "./python[dev]"
python3 verifier/build.py                        # страница-верификатор
sh verifier/go/build.sh                          # бинарь
(cd typescript && npm install && npx tsc)        # SDK на TypeScript

Потом прогнать:

python3 spec/tools/check.py          # 111 проверок векторов формата
go test -C verifier/go ./...         # бинарь
node verifier/tests/conformance.js   # 104 проверки браузерного ядра
cd python && python -m pytest -q     # SDK, восемь атак, сквозные сценарии
cd typescript && npm test            # SDK и сверка четырёх реализаций

Проверено на чистом клоне в контейнере: Debian и Alpine, glibc и musl, amd64 и arm64, Python 3.9, Node 18/20/24, Go 1.22/1.24. Отпечатки совпадают побайтово с теми, что получаются на macOS/arm64.

Границы, о которых надо знать заранее

  • Подпись штампа сверяет только SDK. Корни известных служб вшиты в него, поэтому аргументов не требуется. Бинарь на Go, страница для браузера и верификатор на Node разбора CMS не содержат: они проверяют, что штамп относится именно к этой отметке, и честно сообщают, что подпись не сверена. Служба вне набора — то же самое сообщение с указанием, какой корень нужен; --tsa-ca перекрывает набор.
  • Сертификаты живут меньше, чем срок хранения журнала. Когда сертификат службы истечёт, подпись перестанет сверяться, хотя штамп останется тем же. Верификатор в этом случае говорит «пломба стоит, не подтверждена», а не «подделка». Разбор и варианты решения — в STATE.md, раздел 9.
  • Публичная лента защищает от раздвоения журнала только будучи опубликованной вовне. Лента внутри того же каталога находится под контролем той же стороны, и верификатор говорит это прямым текстом.
  • Локализация расхождения возможна до записи, но не до поля. Отпечаток считается по записи целиком.
  • На сборке python.org для macOS отсутствуют корневые сертификаты — маяк и штамп времени не получатся, пока не выставлен SSL_CERT_FILE или не выполнен Install Certificates.command. Отказ громкий и виден в выводе seal.
  • Принуждение S3 Object Lock обеспечивает AWS, а не эта библиотека. Наша часть — запросить режим COMPLIANCE с верной датой и отказаться писать в корзину без включённой блокировки.

Известное противоречие: вычёркивание и неизменяемое хранение

Две возможности продукта конфликтуют, и об этом надо знать до внедрения, а не после запроса на удаление.

Вычёркивание правит журнал, но не копию, уже уложенную в архив. Что можно сделать:

Приёмник Достигает ли вычёркивание архива
local: да, после aihash archive --date … --force
s3:// в режиме GOVERNANCE да, привилегированным доступом
s3:// в режиме COMPLIANCE нет. Прежнюю версию не удалит никто до истечения срока хранения

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

Как выбирать. Если персональные данные попадают в журнал и вы обязаны исполнять требования об удалении:

  1. Не кладите поля с персональными данными в журнал вовсе — записывайте их идентификаторы, а содержимое держите в системе, где удаление возможно.
  2. Либо укладывайте в архив в режиме GOVERNANCE, приняв, что его можно обойти привилегированным доступом.
  3. Либо ограничьте срок хранения тем, что вы готовы удерживать при любом запросе.

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

Лицензия

Apache-2.0, см. LICENSE.

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

Metadata

Release files for aihash 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for aihash 0.1.0
File Size Uploaded
aihash-0.1.0.tar.gz 114.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for aihash 0.1.0
File Interpreter ABI Platform
aihash-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 200.2 kB

Release files / aihash-0.1.0.tar.gz

Download URL aihash-0.1.0.tar.gz
Size 114.5 kB
Tags Source
SHA-256 checksum
How to use checksums
09c64727224c46db01844a2cd065e48ac9da05ae31dd415e4486c257d37c83df
BLAKE2b-256 checksum
How to use checksums
3a4f3839c517b5341568c1f89472952eaeb8bd6d87ff0d0d4a112f18c290dfdc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.3

Release files / aihash-0.1.0-py3-none-any.whl

Download URL aihash-0.1.0-py3-none-any.whl
Size 85.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
400978c6666dc4ecc377fb7cd6e4f95652fc6658f43e02291750dda6e88b319f
BLAKE2b-256 checksum
How to use checksums
eb06e69eb0271c5764698c6e5f198f6445acaf915c27a7138a44fdfdbd20999d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.3

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page