./research / verifiable-audit-layer

Een verifieerbare auditlaag voor AI.

Een manipulatiebestendige auditlaag voor modelinferentie en agentuitvoering: canonieke serialisatie, hashing met domeinscheiding, DSSE-handtekeningen, een Merkle-DAG per run en een Merkle Mountain Range over runs heen die bewijst dat geen run is verwijderd. Elke primitieve is gekozen uit een directe benchmark tegen de echte alternatieven, met de ruwe data ingecheckt. Deze notitie is het ontwerp en de experimenten erachter.

auditcryptografiebenchmarksshipped

Dreigingsmodel

We veronderstellen een tegenstander die de opslag beheert en records achteraf kan herschrijven, inclusief de operator die ze heeft geproduceerd. Het doel is dat elke bewerking van een verzegelde run detecteerbaar is, dat het record verifieerbaar blijft gedurende een bewaartermijn van jaren, en dat verificatie geen private invoer blootlegt. Twee eigenschappen worden overal apart gehouden: de integriteit van één run en de volledigheid van de verzameling runs. Een bewijs per run geeft het eerste en zegt niets over het tweede, dus beide worden apart ontworpen.

Elke keuze hieronder is op dezelfde manier gemaakt. We beweren niet dat een primitieve de beste is; we vergelijken haar met de geloofwaardige alternatieven op dezelfde machine en checken de ruwe data in, zodat elk getal hier zich in één commando reproduceert. Tijden zijn de snelste van meerdere runs na het verwerpen van de warmup, met behoud van gemiddelde en standaarddeviatie zodat een instabiele meting wordt gemarkeerd in plaats van verborgen. De getallen komen van één AMD64-host op CPython 3.14 en zijn betekenisvol als verhoudingen, niet als absolute waarden.

Experimenten en uitkomsten

Elke mijlpaal was een directe test tegen de echte alternatieven, geen enkele meting. Het overzicht staat hieronder; de gedetailleerde resultaten van elk staan in de secties die volgen.

Mijlpaal Getest tegen Uitkomst
Canonieke vorm naïef JSON, CBOR JCS, byte-identiek, ~3x kosten
Hashing SHA-256, BLAKE3, keccak-256 keccak native 17x, dezelfde bytes
Native kern pip-backends vs Rust sealen 6x, byte-identieke root
Handtekening ECDSA, BLS, ML-DSA Ed25519, 64 B, deterministisch
Post-kwantum ML-DSA 44 / 65 / 87 gefaseerd, verificatie gelijk, 38x grootte
Spoor per run 8 vervalsingsaanvallen, overhead alle gevangen, ~121 us / event
Log over runs gewone Merkle-herbouw MMR, vlak toevoegen, 483x

Canonieke serialisatie

Een capsule moet naar dezelfde bytes hashen in onze Python-kern, onze TypeScript-client en het eigen gereedschap van een auditor, anders faalt een geldig record de verificatie. We gebruiken RFC 8785 (JSON Canonicalization Scheme), dat de getalopmaak vastlegt, de sleutelvolgorde per UTF-16-code-eenheid, en de afwijzing van niet-eindige waarden. We hebben het getest tegen naïef op sleutel gesorteerd JSON en tegen CBOR.

Aanpak Python vs Node Kosten
Naïef gesorteerd JSON wijkt af op Unicode en getallen basislijn
RFC 8785 JCS byte-identiek ongeveer 3x bij serialiseren
CBOR binair, heeft een decoder nodig om te lezen compact

Naïef JSON wijkt tussen runtimes af op Unicode-ordening en getalopmaak, en breekt zo stil de verificatie tussen talen. JCS is byte-identiek in beide, tegen ongeveer 3x de serialisatietijd, één keer per hash betaald en verwaarloosbaar naast het hashen en het omringende netwerkwerk. We houden er precies één implementatie van. Toen onze native module zijn eigen JSON-serialisator meebracht, hebben we die verwijderd, want een tweede canonicalisator die overeenkomt op gewone invoer en afwijkt op de randgevallen die een tegenstander kan kiezen, is slechter dan geen.

Hashing en de native kern

Bladeren worden gehasht met BLAKE3 en Merkle-knopen met keccak-256, beide met RFC 6962-domeinscheiding (aparte voorvoegsels voor bladeren en interne knopen) om de tweede-preimageklasse te sluiten waar een interne knoop als blad wordt gepresenteerd. keccak-256 is niet SHA3-256; de twee delen een permutatie maar verschillen in één padding-byte, en de on-chain-verificateur spreekt keccak, dus een stille vervanging zou elke root on-chain laten falen. De hashinglaag berekent daarom het gevraagde algoritme of werpt een fout, nooit een look-alike, en registreert het algoritme in elke capsule zodat een verificateur de exacte functie reproduceert in plaats van haar af te leiden uit wat lokaal is geïnstalleerd.

Primitieve, 64 B invoer Doorvoer Noot
SHA-256 (stdlib) 1,50M ops/s basislijn, altijd aanwezig
BLAKE3 (pip) 1,44M ops/s gelijk aan SHA-256
keccak-256 (pip) 99k ops/s 15x trager, het knelpunt
keccak-256 (Rust-kern) 1,69M ops/s 17x sneller dan pip

keccak heeft geen standaardbibliotheek-implementatie, en dat maakte de native module de moeite van het bouwen waard. De kloof is het grootst bij invoer ter grootte van een event omdat de kosten van pycryptodome daar objectallocatie per aanroep zijn en niet hashen, precies de grootte die een auditspoor het vaakst hasht; van begin tot eind brengt de native kern het sealen van een run van 1000 events van 78 naar 491 seals per seconde, ongeveer 6x. Cruciaal: het inschakelen van de versneller kan het resultaat niet veranderen: dezelfde run sealen met en zonder de crate levert een bit-identieke root, een conformiteitssuite toetst beide paden aan de door de algoritme-auteurs gepubliceerde vectoren, en CI bouwt één stabiele-ABI (abi3) wheel en verifieert dat ene artefact op elke Python van 3.10 tot 3.14. Het installeren verandert de snelheid en niets anders.

Handtekeningen voor een record van een decennium

Een handtekening op een auditrecord moet zo lang standhouden als het record betwistbaar is, en AI-loggingverplichtingen lopen inmiddels naar een decennium. Als het schema breekt terwijl een record nog in zijn bewaarvenster zit, kan een tegenstander het vervalsen en terugdateren, dus het hele archief faalt in één keer. We hebben Ed25519 gebenchmarkt tegen ECDSA, BLS en de NIST-post-kwantumfamilie ML-DSA, op doorvoer en groottes.

Schema Verificatie (ops/s) Handtekeninggrootte
ECDSA P-256 16 887 ~72 B
ML-DSA-44 (post-kwantum) 10 725 2420 B
Ed25519 10 375 64 B
BLS12-381 alleen referentie-impl. 96 B, aggregeert

Twee resultaten wierpen aannames omver die we hadden meegebracht, en we houden beide op de bon. Ed25519 is niet het snelst te verifiëren: ECDSA P-256 mat ongeveer 1,6x sneller, op geoptimaliseerde bibliotheek-assembly. Ed25519 blijft de standaard op handtekeninggrootte en op determinisme (RFC 8032), dat de ECDSA-nonce-hergebruikfout wegneemt die een private sleutel lekt terwijl handtekeningen nog verifiëren. En post-kwantumhandtekeningen zijn niet traag te verifiëren: ML-DSA-44 wipte net over Ed25519, met de kosten in het tekenen (ongeveer 13x trager) en de grootte (ongeveer 38x de bytes). Aangezien een spoor eenmaal wordt getekend en jaren wordt geverifieerd, is de dure kant de kant die we het minst doen. BLS werd afgewezen op een eis, niet op een getal: aggregatie vouwt veel handtekeningen samen tot 96 bytes, maar een aggregaat verifieert alles-of-niets en heeft elke sleutel en elk bericht nodig, wat onverenigbaar is met het onthullen van één tak van een beslissing zonder de rest prijs te geven.

De herkadering die telt: handtekeningen zijn niet vertrouwelijk, dus "nu oogsten, later ontsleutelen" geldt niet, en de echte dreiging is retroactieve vervalsing binnen het bewaarvenster. Een root verankeren aan een publiek grootboek bewijst wanneer een record bestond onafhankelijk van het handtekeningschema, dus een gebroken sleutel kost het vermogen te bewijzen wie een record verzegelde maar niet wanneer of wat. Verankering ligt daarom op het kritieke pad en ML-DSA staat erachter gefaseerd, met de DSSE-envelop algoritme-wendbaar gehouden en het algoritme per capsule geregistreerd.

Het auditspoor per run

Elke run is een alleen-toevoegende, hash-geketende reeks events (LLM-aanroepen, tool-aanroepen, ophalingen, sub-agent-spawns, menselijke goedkeuringen, guardrail-beslissingen), met een Merkle-root over allemaal en een DSSE-handtekening over de root. Er worden alleen hashes van in- en uitvoer opgeslagen, nooit de payloads, dus het spoor is standaard privacybewarend. De verificateur geeft niet enkel geldig of ongeldig terug; hij noemt de garantie die brak.

Operatie (run van 102 events) Kosten Schaling
Eén event vastleggen ~121 us vlak, onder 0,01% van een stap
Sealen, de root bouwen 0,15 ms lineair in events
Verifiëren, alle bladeren herberekenen 9,2 ms lineair, ongeveer 60x sealen

Vastleggen is minder dan een honderdste procent van een typische agentstap, die door de model-round-trip wordt gedomineerd, dus overhead is geen reden om te samplen en het spoor blijft volledig in plaats van statistisch. Verificatie is veel zwaarder dan sealen omdat het elk blad uit de inhoud herberekent terwijl sealen de boom alleen over reeds aanwezige hashes bouwt. Die asymmetrie is juist voor een auditspoor, waar vastleggen constant en verifiëren zeldzaam is, en ze markeert massale herverificatie als de plek waar de native kern en parallellisme vervolgens lonen. Om de garanties concreet te maken, valt een demo een verzegelde run op acht manieren aan, elk afgewezen met een specifieke reden:

Aanval Afgewezen als
Een modeluitvoer herschrijven bladhash-mismatch
Een event verwijderen of invoegen event-aantal-mismatch
Events herordenen ketenbreuk
De run afkappen ketenkop-mismatch
Het hash-algoritme downgraden bladhash-mismatch
Onder de verkeerde sleutel presenteren handtekening verifieert niet

Het transparantielog over runs

Een geldige capsule per run kan niet bewijzen dat de verzameling runs volledig is. Een operator die een onwelgevallige run laat vallen, houdt een collectie individueel perfecte capsules en een geschiedenis met een gat. Dat sluiten we met één alleen-toevoegend log van capsule-roots dat inclusiebewijzen (een run zit in het log) en consistentiebewijzen (het log op een eerdere grootte is een exact voorvoegsel van het log nu) ondersteunt. Consistentie betrapt censuur: een herbouwd log met een verwijderde run is intern welgevormd en elke overlevende capsule verifieert nog, maar het faalt een consistentiecheck tegen de eerder gepubliceerde root, en dezelfde check wijst een teruggedateerde invoeging af. Dit werkt alleen tegen een root die vóór de manipulatie is vastgelegd, dus die root moet ergens leven dat de operator niet kan herzien, zoals een publiek anker.

We gebruiken een Merkle Mountain Range in plaats van een gewone Merkle-boom omdat een toevoeging alleen knopen toevoegt en er nooit een herschrijft, wat het log op write-once- of objectopslag laat leven en oude inclusiebewijzen geldig houdt naarmate het log groeit. Die eigenschap is bijna optimaal, niet slechts handig: ePrint 2025/234 bewijst dat elke bondige alleen-toevoegende commitment een superlineair aantal getuige-updates moet induceren, en een nabije MMR-variant haalt de grens in wezen.

Log-grootte MMR-bouw Gewone herbouw
500 1,3 ms 158 ms (120x)
1000 2,6 ms 618 ms (237x)
2000 5,2 ms 2491 ms (483x)

Toevoegen blijft vlak naarmate het log groeit (ongeveer 390k toevoegingen per seconde bij zowel 100 als 10 000 entries) terwijl het herbouwen van een gewone boom daalt van 8176 naar 84 roots per seconde over hetzelfde bereik, de kwadratische-versus-linearitmische signatuur. De opslag stabiliseert op 2,00 opgeslagen hashes per entry en inclusiebewijzen groeien slechts van 7 naar 17 stappen tussen 100 en 100 000 entries. Waar we verliezen: onze consistentiebewijzen zijn O(log^2 n), met één pad per piek van het oude log in plaats van het haalbare O(log n), een paar kilobytes bij 100 000 entries en een bewuste ruil voor een constructie die een reviewer lezend kan controleren.

Grenzen die we niet overschrijden

Hashing geeft manipulatiebewijs, geen manipulatiebestendigheid. Een tegenstander die een run herbouwt en zijn root herberekent, produceert een zelfconsistent record, want een Merkle-root bewijst dat de events overeenkomen met de root, niet dat de root de gepubliceerde is. De handtekening betrapt dat, en het publieke anker bewijst het tijdstip. Het transparantielog rust in het groot op dezelfde aanname: het detecteert een verwijderde run alleen ten opzichte van een root die vóór de verwijdering is vastgelegd. We benoemen deze grenzen in het product en zijn demo's, en een test verzekert dat de vervalsingsdemo de aanval blijft tonen die we niet stoppen, zodat het eerlijke geval niet stilletjes kan verdwijnen.

Reproduceerbaarheid

Elk getal hier komt uit een ingecheckte benchmark die in één commando opnieuw draait, naast de tests, de conformiteitssuites tussen talen en tussen implementaties, en de CI-matrix die Python 3.10 tot 3.14 met en zonder de native kern uitoefent. De methodologie, de ruwe JSON en de beslissingsnotities (inclusief de dimensies waar elke keuze verliest) staan in de repository, want een laag die AI auditeerbaar maakt, moet zelf auditeerbaar zijn.