./research / verifiable-audit-layer

Перевірюваний шар аудиту для ШІ.

Захищений від підробки шар аудиту для інференсу моделей та виконання агентів: канонічна серіалізація, гешування з розділенням доменів, підписи DSSE, Merkle-DAG на кожен запуск і міжзапусковий Merkle Mountain Range, що доводить: жодного запуску не було видалено. Кожен примітив обрано за прямим бенчмарком проти реальних альтернатив, із зафіксованими сирими даними. Ця нотатка і є проєкт та експерименти, що стоять за ним.

аудиткриптографіябенчмаркиshipped

Модель загроз

Ми припускаємо супротивника, який контролює сховище і може переписувати записи заднім числом, включно з оператором, що їх створив. Мета в тому, щоб будь-яке редагування запечатаного запуску було виявним, щоб запис лишався перевірюваним упродовж терміну зберігання, вимірюваного роками, і щоб перевірка не розкривала жодних приватних вхідних даних. Дві властивості весь час тримаються окремо: цілісність одного запуску та повнота множини запусків. Квитанція на запуск дає першу і нічого не каже про другу, тож обидві конструюються окремо.

Кожен вибір нижче зроблено однаково. Ми не стверджуємо, що примітив найкращий; ми порівнюємо його з правдоподібними альтернативами на тій самій машині і фіксуємо сирі дані, тож будь-яке число тут відтворюється однією командою. Часи — найшвидший із кількох прогонів після відкидання розігріву, зі збереженими середнім і стандартним відхиленням, щоб нестабільний вимір позначався, а не ховався. Числа отримано на одному хості AMD64 на CPython 3.14 і мають сенс як відношення, а не як абсолютні величини.

Експерименти та результати

Кожна віха була прямим тестом проти реальних альтернатив, а не одиничним виміром. Огляд нижче; детальні результати кожної — у наступних розділах.

Віха Тестовано проти Результат
Канонічна форма наївний JSON, CBOR JCS, ідентично побайтово, ~3x вартості
Гешування SHA-256, BLAKE3, keccak-256 keccak нативно 17x, ті самі байти
Нативне ядро бекенди pip vs Rust запечатування 6x, корінь ідентичний побайтово
Підпис ECDSA, BLS, ML-DSA Ed25519, 64 B, детермінований
Постквантовий ML-DSA 44 / 65 / 87 поетапно, перевірка нарівні, 38x розміру
Слід на запуск 8 атак підробки, накладні усі впіймані, ~121 us / подія
Міжзапусковий лог проста перебудова Merkle MMR, пласке додавання, 483x

Канонічна серіалізація

Капсула має гешуватися в ті самі байти в нашому ядрі на Python, у нашому клієнті на TypeScript і у власному інструменті аудитора, інакше дійсний запис не пройде перевірку. Ми використовуємо RFC 8785 (JSON Canonicalization Scheme), який фіксує форматування чисел, порядок ключів за кодовою одиницею UTF-16 і відхилення нескінченних значень. Ми тестували його проти JSON із наївним сортуванням ключів і проти CBOR.

Підхід Python vs Node Вартість
Наївно відсортований JSON розходиться на Unicode і числах базова лінія
RFC 8785 JCS ідентично побайтово близько 3x при серіалізації
CBOR двійковий, для читання потрібен декодер компактний

Наївний JSON розходиться між середовищами виконання в порядку Unicode і форматуванні чисел, тож тихо ламає міжмовну перевірку. JCS ідентичний побайтово в обох, за приблизно 3x часу серіалізації, сплачуваного раз на геш і незначного поряд із гешуванням та мережевою роботою навколо. Ми тримаємо рівно одну його реалізацію. Коли наш нативний модуль приніс власний серіалізатор JSON, ми його прибрали, бо другий канонізатор, який збігається на звичайних входах і розходиться на межових випадках, які може обрати супротивник, гірший за жоден.

Гешування та нативне ядро

Листя гешується BLAKE3, а вузли Merkle — keccak-256, обидва з розділенням доменів за RFC 6962 (різні префікси для листя та внутрішніх вузлів), щоб закрити клас другого прообразу, де внутрішній вузол видають за лист. keccak-256 — це не SHA3-256; обидва ділять перестановку, але різняться одним байтом набивки, а он-чейн-верифікатор говорить keccak, тож тиха підміна змусила б кожен корінь падати он-чейн. Тому шар гешування обчислює запитаний алгоритм або кидає виняток, ніколи не двійника, і записує алгоритм у кожній капсулі, щоб верифікатор відтворював точну функцію, а не виводив її з того, що встановлено локально.

Примітив, вхід 64 B Пропускна здатність Примітка
SHA-256 (stdlib) 1,50M ops/s базова лінія, завжди є
BLAKE3 (pip) 1,44M ops/s нарівні з SHA-256
keccak-256 (pip) 99k ops/s у 15x повільніше, вузьке місце
keccak-256 (ядро на Rust) 1,69M ops/s у 17x швидше за pip

keccak не має реалізації в стандартній бібліотеці, що й зробило нативний модуль вартим побудови. Розрив найбільший на входах розміром з подію, бо витрати pycryptodome там — це виділення обʼєктів на виклик, а не гешування, саме той розмір, який слід аудиту гешує найчастіше; наскрізно нативне ядро піднімає запечатування запуску на 1000 подій із 78 до 491 запечатування за секунду, близько 6x. Головне: увімкнення прискорювача не може змінити результат: запечатування того самого запуску з крейтом і без нього дає корінь, ідентичний побітово; набір тестів відповідності звіряє обидва шляхи з векторами, опублікованими авторами алгоритму, а CI збирає одне колесо зі стабільним ABI (abi3) і перевіряє цей єдиний артефакт на кожному Python від 3.10 до 3.14. Встановлення змінює швидкість і нічого більше.

Підписи для запису завдовжки в десятиліття

Підпис на аудиторському записі має триматися стільки, скільки запис можна оскаржити, а зобовʼязання щодо логування ШІ вже тягнуться до десятиліття. Якщо схема ламається, поки запис ще у своєму вікні зберігання, супротивник може підробити його й датувати заднім числом, тож весь архів падає разом. Ми порівняли Ed25519 проти ECDSA, BLS і постквантової родини NIST ML-DSA, за пропускною здатністю та розмірами.

Схема Перевірка (ops/s) Розмір підпису
ECDSA P-256 16 887 ~72 B
ML-DSA-44 (постквантова) 10 725 2420 B
Ed25519 10 375 64 B
BLS12-381 лише референс-реалізація 96 B, агрегує

Два результати спростували припущення, які ми принесли з собою, і обидва ми тримаємо в записі. Ed25519 не найшвидший для перевірки: ECDSA P-256 виміряно приблизно в 1,6x швидше, на оптимізованому асемблері бібліотеки. Ed25519 лишається типовим за розміром підпису та за детермінізмом (RFC 8032), який усуває відмову ECDSA від повторного використання nonce, що витікає приватний ключ, поки підписи ще перевіряються. І постквантові підписи не повільні на перевірці: ML-DSA-44 ледь випередив Ed25519, а його ціна — у підписуванні (приблизно в 13x повільніше) і в розмірі (приблизно в 38x байтів). Оскільки слід підписується раз і перевіряється роками, дорогий бік — той, що ми робимо найменше. BLS відхилено за вимогою, а не за числом: агрегація згортає багато підписів у 96 байтів, але агрегат перевіряється за принципом усе-або-нічого й потребує кожного ключа та повідомлення, що несумісно з розкриттям однієї гілки рішення без розкриття решти.

Переосмислення, яке важить: підписи не конфіденційні, тож «збери зараз, розшифруй потім» не застосовне, а справжня загроза — ретроактивна підробка в межах вікна зберігання. Прив'язка кореня до публічного реєстру доводить, коли запис існував, незалежно від схеми підпису, тож зламаний ключ коштує здатності довести, хто запечатав запис, але не коли й не що. Тому прив'язка лежить на критичному шляху, а ML-DSA вишикувано за нею поетапно, при цьому конверт DSSE лишається гнучким за алгоритмом, а алгоритм записується на кожну капсулу.

Слід аудиту на кожен запуск

Кожен запуск — це лише-додавана, зчеплена гешем послідовність подій (виклики LLM, виклики інструментів, витяги, породження субагентів, людські схвалення, рішення огорож), із коренем Merkle над усіма ними та підписом DSSE над коренем. Зберігаються лише геші входів і виходів, ніколи самі корисні навантаження, тож слід за замовчуванням зберігає приватність. Верифікатор не просто повертає «дійсний» чи «недійсний»; він називає гарантію, що зламалася.

Операція (запуск на 102 події) Вартість Масштабування
Записати одну подію ~121 us пласко, нижче 0,01% кроку
Запечатати, побудувати корінь 0,15 ms лінійно за подіями
Перевірити, перерахувати всі листки 9,2 ms лінійно, близько 60x запечатування

Запис — менше сотої частки відсотка типового кроку агента, у якому домінує круговий обхід до моделі, тож накладні не привід семплувати, і слід лишається повним, а не статистичним. Перевірка значно важча за запечатування, бо перераховує кожен листок із вмісту, тоді як запечатування лише будує дерево над уже наявними гешами. Ця асиметрія правильна для аудиторського сліду, де запис сталий, а перевірка рідкісна, і вона позначає масову переперевірку як місце, де нативне ядро й паралелізм окупляться далі. Щоб зробити гарантії конкретними, демо атакує запечатаний запуск вісьмома способами, кожен відхилено з конкретною причиною:

Атака Відхилено як
Переписати вивід моделі розбіжність гешу листка
Видалити або вставити подію розбіжність кількості подій
Переставити події розрив ланцюга
Обрізати запуск розбіжність голови ланцюга
Знизити алгоритм гешу розбіжність гешу листка
Подати під неправильним ключем підпис не перевіряється

Міжзапусковий журнал прозорості

Дійсна капсула на запуск не може довести, що множина запусків повна. Оператор, що відкидає незручний запуск, тримає колекцію окремо бездоганних капсул та історію з дірою. Ми закриваємо це єдиним лише-додаваним журналом коренів капсул, який підтримує докази включення (запуск є в журналі) та докази узгодженості (журнал попереднього розміру — точний префікс журналу тепер). Узгодженість ловить цензуру: перебудований журнал із видаленим запуском внутрішньо коректний, і кожна вціліла капсула ще перевіряється, але він не проходить перевірку узгодженості проти раніше опублікованого кореня, і та сама перевірка відхиляє вставку заднім числом. Це працює лише проти кореня, зафіксованого до підробки, тож той корінь має жити десь, чого оператор не може переглянути, як-от у публічному якорі.

Ми використовуємо Merkle Mountain Range, а не просте дерево Merkle, бо додавання лише додає вузли й ніколи не переписує жодного, що дає журналу жити на write-once- або обʼєктному сховищі й тримає старі докази включення дійсними, поки журнал росте. Ця властивість близька до оптимальної, а не просто зручна: ePrint 2025/234 доводить, що будь-яке стисле лише-додаване зобовʼязання має спричиняти суперлінійну кількість оновлень свідків, а близький варіант MMR по суті досягає межі.

Розмір журналу Побудова MMR Проста перебудова
500 1,3 ms 158 ms (120x)
1000 2,6 ms 618 ms (237x)
2000 5,2 ms 2491 ms (483x)

Додавання лишається пласким, поки журнал росте (близько 390k додавань за секунду і при 100, і при 10 000 записах), тоді як перебудова простого дерева падає з 8176 до 84 коренів за секунду на тому самому діапазоні — квадратична-проти-лінеаритмічної підпис. Сховище усталюється на 2,00 збереженого гешу на запис, а докази включення ростуть лише з 7 до 17 кроків між 100 і 100 000 записами. Де ми програємо: наші докази узгодженості мають O(log^2 n), несучи один шлях на пік старого журналу замість досяжного O(log n), кілька кілобайтів при 100 000 записах і свідомий розмін заради конструкції, яку рецензент може перевірити читанням.

Межі, які ми не переступаємо

Гешування дає доказовість підробки, а не захист від неї. Супротивник, що перебудовує запуск і перераховує його корінь, продукує самоузгоджений запис, бо корінь Merkle доводить, що події відповідають кореню, а не те, що корінь — саме опублікований. Підпис ловить це, а публічний якір доводить час. Журнал прозорості у великому спирається на те саме припущення: він виявляє видалений запуск лише відносно кореня, зафіксованого до видалення. Ми заявляємо ці межі в продукті та його демо, і тест стверджує, що демо підробки далі показує атаку, яку ми не спиняємо, щоб чесний випадок не міг тихо зникнути.

Відтворюваність

Кожне число тут походить із зафіксованого бенчмарка, що перезапускається однією командою, поряд із тестами, наборами відповідності між мовами та між реалізаціями, і CI-матрицею, що проганяє Python від 3.10 до 3.14 з нативним ядром і без нього. Методологія, сирий JSON і нотатки про рішення (включно з вимірами, де кожен вибір програє) — у репозиторії, бо шар, що робить ШІ придатним до аудиту, сам має бути придатним до аудиту.