Catalog PyPI
PACKAGE EXPLORERView in registry

aihash

PyPI

Доказуемый журнал работы ИИ-системы: сохранность и пломба

ai-agentsauditaudit-logcompliancegdprllmprovenancerfc3161tamper-evidenttimestamping
Versions collected1
Direct dependencies7From release 0.1.0
Last observed21 Oct, 10:51 UTC

README

Version 0.1.0 documentation

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.

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

Downloads & popularity

Awaiting observations

No metric observations yet

Download counts and popularity will appear here as the collector receives them.

Versions 1

Collected releases
VersionPublishedDirect dependenciesOpen version
0.1.030 Jul 20267

Package advisories

0
No matching advisories collected

This describes our collected data; it is not a security assessment.

Repository

PENDING
jacobb-del/aihash

Shared repository data · GitHub

License detected in repository
Not collected

Metadata history

Meaningful changes
Current collected state Current

Observed since 21 Oct, 10:51 UTC

No superseded metadata states collected. Unchanged observations do not create new revisions.

Record identity

package:17c3b446b6e3ac8ec4a462c6d245d04d469014623343acc7cfad97d39008e8f6