./research / verifiable-audit-layer
Ověřitelná auditní vrstva pro AI.
Auditní vrstva prokazující neoprávněný zásah pro inferenci modelů a běh agentů: kanonická serializace, hashování s oddělením domén, podpisy DSSE, Merkle DAG na každý běh a mezi-běhový Merkle Mountain Range, který dokazuje, že žádný běh nebyl smazán. Každý primitiv byl vybrán z přímého benchmarku proti reálným alternativám, se syrovými daty v repozitáři. Tato poznámka je návrh a experimenty za ním.
Model hrozby
Předpokládáme protivníka, který ovládá úložiště a může zpětně přepisovat záznamy, včetně operátora, který je vytvořil. Cílem je, aby jakákoli úprava zapečetěného běhu byla zjistitelná, aby záznam zůstal ověřitelný po dobu uchování měřenou v letech, a aby ověření neodhalilo žádné soukromé vstupy. Dvě vlastnosti jsou po celou dobu drženy odděleně: integrita jednoho běhu a úplnost množiny běhů. Potvrzení na běh dává první a o druhé neříká nic, takže obě se konstruují zvlášť.
Každá volba níže byla učiněna stejně. Netvrdíme, že primitiv je nejlepší; porovnáváme jej s věrohodnými alternativami na stejném stroji a commitujeme syrová data, takže každé číslo zde se reprodukuje jedním příkazem. Časy jsou nejrychlejší z několika běhů po zahození zahřátí, s uchovaným průměrem a směrodatnou odchylkou, aby nestabilní měření bylo označeno, ne skryto. Čísla pocházejí z jediného hostitele AMD64 na CPython 3.14 a jsou smysluplná jako poměry, ne jako absolutní hodnoty.
Experimenty a výsledky
Každý milník byl přímý test proti reálným alternativám, ne jediné měření. Přehled je níže; podrobné výsledky každého jsou v následujících sekcích.
| Milník | Testováno proti | Výsledek |
|---|---|---|
| Kanonická forma | naivní JSON, CBOR | JCS, bajt po bajtu shodné, ~3x náklady |
| Hashování | SHA-256, BLAKE3, keccak-256 | keccak nativně 17x, stejné bajty |
| Nativní jádro | backendy pip vs Rust | pečetění 6x, kořen bajt po bajtu shodný |
| Podpis | ECDSA, BLS, ML-DSA | Ed25519, 64 B, deterministický |
| Postkvantový | ML-DSA 44 / 65 / 87 | po etapách, ověření srovnatelné, 38x velikost |
| Stopa na běh | 8 útoků padělání, režie | všechny zachyceny, ~121 us / událost |
| Mezi-běhový log | prostá přestavba Merkle | MMR, ploché připojení, 483x |
Kanonická serializace
Kapsle se musí hashovat na stejné bajty v našem jádru Python, v našem klientu TypeScript i v auditorově vlastním nástroji, jinak platný záznam neprojde ověřením. Používáme RFC 8785 (JSON Canonicalization Scheme), který fixuje formátování čísel, pořadí klíčů podle kódové jednotky UTF-16 a odmítání nekonečných hodnot. Testovali jsme jej proti JSON s naivně seřazenými klíči a proti CBOR.
| Přístup | Python vs Node | Náklady |
|---|---|---|
| Naivně seřazený JSON | rozchází se na Unicode a číslech | základ |
| RFC 8785 JCS | bajt po bajtu shodné | asi 3x při serializaci |
| CBOR | binární, ke čtení potřebuje dekodér | úsporný |
Naivní JSON se mezi běhovými prostředími rozchází v řazení Unicode a formátování čísel, a tak tiše láme ověření mezi jazyky. JCS je v obou bajt po bajtu shodný, za asi 3x čas serializace, placený jednou na hash a zanedbatelný vedle hashování a síťové práce kolem. Držíme přesně jednu jeho implementaci. Když náš nativní modul přinesl vlastní serializátor JSON, odstranili jsme jej, protože druhý kanonikalizátor, který se shoduje na běžných vstupech a rozchází se na hraničních případech, jež si protivník může zvolit, je horší než žádný.
Hashování a nativní jádro
Listy se hashují pomocí BLAKE3 a uzly Merkle pomocí keccak-256, oba s oddělením domén podle RFC 6962 (odlišné prefixy pro listy a vnitřní uzly), aby se uzavřela třída druhého vzoru, kde je vnitřní uzel vydáván za list. keccak-256 není SHA3-256; oba sdílejí permutaci, ale liší se jedním bajtem výplně, a on-chain ověřovatel mluví keccak, takže tichá záměna by způsobila, že každý kořen selže on-chain. Vrstva hashování proto počítá požadovaný algoritmus nebo vyhodí chybu, nikdy dvojníka, a zaznamenává algoritmus v každé kapsli, aby ověřovatel reprodukoval přesnou funkci místo jejího odvozování z toho, co je nainstalováno lokálně.
| Primitiv, vstup 64 B | Propustnost | Poznámka |
|---|---|---|
| SHA-256 (stdlib) | 1,50M ops/s | základ, vždy přítomen |
| BLAKE3 (pip) | 1,44M ops/s | srovnatelný se SHA-256 |
| keccak-256 (pip) | 99k ops/s | 15x pomalejší, úzké hrdlo |
| keccak-256 (jádro Rust) | 1,69M ops/s | 17x rychlejší než pip |
keccak nemá implementaci ve standardní knihovně, což učinilo nativní modul hodným postavení. Rozdíl je největší u vstupů velikosti události, protože náklady pycryptodome jsou tam alokace objektů na volání, ne hashování, přesně ta velikost, kterou auditní stopa hashuje nejčastěji; od začátku do konce nativní jádro zvedne pečetění běhu o 1000 událostech ze 78 na 491 pečetí za sekundu, asi 6x. Zásadní: zapnutí akcelerátoru nemůže změnit výsledek: zapečetění téhož běhu s craten a bez něj dá bit po bitu shodný kořen, sada shody prověří obě cesty proti vektorům publikovaným autory algoritmu, a CI sestaví jediné kolo se stabilním ABI (abi3) a ověří tento jediný artefakt na každém Pythonu od 3.10 do 3.14. Instalace mění rychlost a nic jiného.
Podpisy pro záznam na desetiletí
Podpis na auditním záznamu musí vydržet tak dlouho, dokud lze záznam zpochybnit, a povinnosti logování AI nyní míří k desetiletí. Pokud se schéma zlomí, zatímco je záznam ještě ve svém okně uchování, protivník jej může zfalšovat a zpětně datovat, takže celý archiv padne najednou. Porovnali jsme Ed25519 proti ECDSA, BLS a postkvantové rodině NIST ML-DSA, na propustnosti a velikostech.
| Schéma | Ověření (ops/s) | Velikost podpisu |
|---|---|---|
| ECDSA P-256 | 16 887 | ~72 B |
| ML-DSA-44 (postkvantový) | 10 725 | 2420 B |
| Ed25519 | 10 375 | 64 B |
| BLS12-381 | jen referenční impl. | 96 B, agreguje |
Dva výsledky vyvrátily předpoklady, které jsme si přinesli, a oba držíme v záznamu. Ed25519 není nejrychlejší na ověření: ECDSA P-256 naměřilo asi 1,6x rychleji, na optimalizovaném knihovním assembleru. Ed25519 zůstává výchozím díky velikosti podpisu a determinismu (RFC 8032), který odstraňuje selhání opětovného použití nonce u ECDSA, jež uniká soukromý klíč, zatímco podpisy stále ověřují. A postkvantové podpisy nejsou pomalé na ověření: ML-DSA-44 těsně předčilo Ed25519, s náklady v podepisování (asi 13x pomalejší) a velikosti (asi 38x bajtů). Protože stopa se podepíše jednou a ověřuje se roky, drahá strana je ta, kterou děláme nejméně. BLS byl zamítnut na požadavku, ne na čísle: agregace složí mnoho podpisů do 96 bajtů, ale agregát ověřuje vše-nebo-nic a potřebuje každý klíč a zprávu, což je neslučitelné se zveřejněním jedné větve rozhodnutí bez odhalení zbytku.
Přerámování, na kterém záleží: podpisy nejsou důvěrné, takže „sklízej teď, dešifruj později" neplatí, a skutečnou hrozbou je zpětné padělání uvnitř okna uchování. Zakotvení kořene do veřejné účetní knihy dokazuje, kdy záznam existoval, nezávisle na schématu podpisu, takže zlomený klíč stojí schopnost dokázat, kdo záznam zapečetil, ale ne kdy nebo co. Zakotvení je proto na kritické cestě a ML-DSA je za ním po etapách, přičemž obálka DSSE zůstává algoritmicky hbitá a algoritmus se zaznamenává na kapsli.
Auditní stopa na běh
Každý běh je sekvence událostí pouze pro připojení a zřetězená hashem (volání LLM, volání nástrojů, načtení, spawny podagentů, lidská schválení, rozhodnutí zábradlí), s kořenem Merkle nad všemi a podpisem DSSE nad kořenem. Ukládají se jen hashe vstupů a výstupů, nikdy náklady, takže stopa ve výchozím stavu chrání soukromí. Ověřovatel nevrací pouze platné nebo neplatné; pojmenuje záruku, která se zlomila.
| Operace (běh o 102 událostech) | Náklady | Škálování |
|---|---|---|
| Zaznamenat jednu událost | ~121 us | ploché, pod 0,01 % kroku |
| Zapečetit, sestavit kořen | 0,15 ms | lineární v událostech |
| Ověřit, přepočítat všechny listy | 9,2 ms | lineární, asi 60x pečetění |
Zaznamenávání je méně než setina procenta typického kroku agenta, jemuž dominuje výprava k modelu, takže režie není důvod vzorkovat a stopa zůstává úplná, ne statistická. Ověření je mnohem těžší než pečetění, protože přepočítává každý list z obsahu, zatímco pečetění staví strom jen nad již přítomnými hashi. Ta asymetrie je pro auditní stopu správná, kde zaznamenávání je konstantní a ověřování vzácné, a označuje hromadné přeověření jako místo, kde se nativní jádro a paralelismus vyplatí příště. Aby byly záruky konkrétní, demo útočí na zapečetěný běh osmi způsoby, každý zamítnut s konkrétním důvodem:
| Útok | Zamítnut jako |
|---|---|
| Přepsat výstup modelu | neshoda hashe listu |
| Smazat nebo vložit událost | neshoda počtu událostí |
| Přeuspořádat události | zlom řetězu |
| Oříznout běh | neshoda hlavy řetězu |
| Snížit algoritmus hashe | neshoda hashe listu |
| Předložit pod špatným klíčem | podpis se neověří |
Mezi-běhový log transparentnosti
Platná kapsle na běh nemůže dokázat, že množina běhů je úplná. Operátor, který zahodí nepohodlný běh, drží sbírku jednotlivě dokonalých kapslí a historii s dírou. Uzavíráme to jediným logem pouze pro připojení kořenů kapslí, který podporuje důkazy zahrnutí (běh je v logu) a důkazy konzistence (log dřívější velikosti je přesným prefixem logu nyní). Konzistence chytá cenzuru: přestavěný log s odebraným během je vnitřně dobře utvořený a každá přeživší kapsle se stále ověřuje, ale selže při kontrole konzistence proti dříve publikovanému kořeni, a tatáž kontrola zamítne zpětně datované vložení. To funguje jen proti kořeni zavázanému před manipulací, takže ten kořen musí žít někde, co operátor nemůže revidovat, jako veřejná kotva.
Používáme Merkle Mountain Range místo prostého stromu Merkle, protože připojení jen přidává uzly a nikdy žádný nepřepisuje, což nechává log žít na úložišti zápisu-jednou nebo objektovém a udržuje staré důkazy zahrnutí platné, jak log roste. Ta vlastnost je téměř optimální, ne pouze pohodlná: ePrint 2025/234 dokazuje, že jakýkoli stručný závazek pouze pro připojení musí vyvolat superlineární počet aktualizací svědků, a blízká varianta MMR v podstatě dosahuje meze.
| Velikost logu | Stavba MMR | Prostá přestavba |
|---|---|---|
| 500 | 1,3 ms | 158 ms (120x) |
| 1000 | 2,6 ms | 618 ms (237x) |
| 2000 | 5,2 ms | 2491 ms (483x) |
Připojování zůstává ploché, jak log roste (asi 390k připojení za sekundu při 100 i 10 000 záznamech), zatímco přestavba prostého stromu klesá z 8176 na 84 kořeny za sekundu na stejném rozsahu, kvadratická-versus-linearitmická signatura. Úložiště se ustálí na 2,00 uloženého hashe na záznam a důkazy zahrnutí rostou jen ze 7 na 17 kroků mezi 100 a 100 000 záznamy. Kde prohráváme: naše důkazy konzistence jsou O(log^2 n), nesou jednu cestu na vrchol starého logu místo dosažitelného O(log n), pár kilobajtů při 100 000 záznamech a záměrný obchod za konstrukci, kterou recenzent může zkontrolovat čtením.
Meze, které nepřekračujeme
Hashování dává průkaznost neoprávněného zásahu, ne odolnost vůči němu. Protivník, který přestaví běh a přepočítá jeho kořen, vyprodukuje sebekonzistentní záznam, protože kořen Merkle dokazuje, že události odpovídají kořeni, ne že kořen je ten publikovaný. Podpis to chytá a veřejná kotva dokazuje čas. Log transparentnosti spočívá ve velkém na témž předpokladu: smazaný běh detekuje jen relativně ke kořeni zavázanému před smazáním. Tyto hranice deklarujeme v produktu a jeho demech a test tvrdí, že demo padělání dál ukazuje útok, který nezastavujeme, aby tento poctivý případ nemohl tiše zmizet.
Reprodukovatelnost
Každé číslo zde pochází z commitnutého benchmarku, který se znovu spustí jedním příkazem, vedle testů, sad shody mezi jazyky a mezi implementacemi, a CI matice, jež procvičuje Python 3.10 až 3.14 s nativním jádrem i bez něj. Metodika, syrový JSON a poznámky k rozhodnutím (včetně dimenzí, kde každá volba prohrává) jsou v repozitáři, protože vrstva, která činí AI auditovatelnou, sama musí být auditovatelná.