Skip to content

Repository files navigation

aihash

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

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

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

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

pip install ./python
aihash verify ./demo/journal
The chain matches end to end.
Days sealed: 1 (2026-07-29)
  2026-07-29: rfc3161 — timestamp authority signature verified (root store version 1, root freetsa)

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

DOES NOT MATCH

  stream voice-eu-3 seq=3: the link does not match the recorded one

Код возврата 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
Verdict: the content matches its fingerprint and is in the daily tree.
A seal was placed for these days but is not confirmed here — see
its line above. Integrity is not proven until verifying the seal
is finished; this is not the same as having no seal.
The record existed no earlier than 29.07.2026 08:45 UTC (drand:quicknet round 30837112)
and no later than the seal.

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

aihash verify ep.seal
  seal rfc3161: verified — timestamp authority signature verified
                (root store version 1, root freetsa)

Verdict: the content matches its fingerprint, the fingerprint is
in the daily tree, and the daily checkpoint is sealed.
The record existed no earlier than 29.07.2026 08:45 UTC (drand:quicknet round 30837112)
and no later than 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.

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

About

Журнал работы ИИ-системы, который нельзя незаметно изменить задним числом: пломбу на записи ставит третья сторона, проверка работает офлайн

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages