./research / verifiable-audit-layer

Ett verifierbart granskningslager för AI.

Ett manipuleringsröjande granskningslager för modellinferens och agentkörning: kanonisk serialisering, domänseparerad hashning, DSSE-signaturer, en Merkle-DAG per körning och en Merkle Mountain Range över körningar som bevisar att ingen körning raderats. Varje primitiv valdes ur en direkt benchmark mot de verkliga alternativen, med rådata incheckad. Den här noten är designen och experimenten bakom den.

granskningkryptografibenchmarksshipped

Hotmodell

Vi antar en motståndare som kontrollerar lagringen och kan skriva om poster i efterhand, inklusive operatören som skapade dem. Målet är att varje redigering av en förseglad körning ska vara upptäckbar, att posten förblir verifierbar under en lagringsperiod mätt i år, och att verifieringen inte exponerar någon privat indata. Två egenskaper hålls åtskilda genomgående: integriteten hos en enskild körning och fullständigheten hos mängden körningar. Ett kvitto per körning ger den första och säger inget om den andra, så båda konstrueras separat.

Varje val nedan gjordes på samma sätt. Vi påstår inte att en primitiv är bäst; vi jämför den med de trovärdiga alternativen på samma maskin och checkar in rådatan, så att varje siffra här reproduceras med ett kommando. Tiderna är den snabbaste av flera körningar efter att uppvärmningen kastats, med medelvärde och standardavvikelse bevarade så att en instabil mätning flaggas i stället för döljs. Siffrorna kommer från en enda AMD64-värd på CPython 3.14 och är meningsfulla som förhållanden, inte som absoluta värden.

Experiment och utfall

Varje milstolpe var ett direkt test mot de verkliga alternativen, inte en enskild mätning. Översikten finns nedan; de detaljerade resultaten för var och en finns i avsnitten som följer.

Milstolpe Testad mot Utfall
Kanonisk form naiv JSON, CBOR JCS, byte-identisk, ~3x kostnad
Hashning SHA-256, BLAKE3, keccak-256 keccak nativt 17x, samma byte
Nativ kärna pip-backends vs Rust försegling 6x, byte-identisk rot
Signatur ECDSA, BLS, ML-DSA Ed25519, 64 B, deterministisk
Postkvant ML-DSA 44 / 65 / 87 stegvis, verifiering i nivå, 38x storlek
Spår per körning 8 förfalskningsattacker, overhead alla fångade, ~121 us / händelse
Logg över körningar vanlig Merkle-ombyggnad MMR, platt tillägg, 483x

Kanonisk serialisering

En kapsel måste hasha till samma byte i vår Python-kärna, vår TypeScript-klient och en granskares eget verktyg, annars misslyckas en giltig post med verifieringen. Vi använder RFC 8785 (JSON Canonicalization Scheme), som fixerar talformatering, nyckelordning per UTF-16-kodenhet och avvisning av icke-ändliga värden. Vi testade den mot naivt nyckelsorterad JSON och mot CBOR.

Ansats Python vs Node Kostnad
Naivt sorterad JSON avviker på Unicode och tal baslinje
RFC 8785 JCS byte-identisk ungefär 3x vid serialisering
CBOR binär, behöver en avkodare för att läsas kompakt

Naiv JSON avviker mellan körtider på Unicode-ordning och talformatering, och bryter så tyst verifieringen mellan språk. JCS är byte-identisk i båda, till ungefär 3x serialiseringstiden, betald en gång per hash och försumbar bredvid hashningen och nätverksarbetet runt omkring. Vi håller exakt en implementation av den. När vår nativa modul kom med sin egen JSON-serialiserare tog vi bort den, för en andra kanonikaliserare som stämmer på vanliga indata och avviker på gränsfallen en motståndare kan välja är sämre än ingen.

Hashning och den nativa kärnan

Löv hashas med BLAKE3 och Merkle-noder med keccak-256, båda med RFC 6962-domänseparation (skilda prefix för löv och inre noder) för att stänga andra-förbild-klassen där en inre nod presenteras som ett löv. keccak-256 är inte SHA3-256; de två delar en permutation men skiljer sig i en utfyllnadsbyte, och on-chain-verifieraren talar keccak, så en tyst substitution skulle få varje rot att misslyckas on-chain. Hashningslagret beräknar därför den begärda algoritmen eller kastar ett fel, aldrig en dubbelgångare, och antecknar algoritmen i varje kapsel så att en verifierare reproducerar den exakta funktionen i stället för att härleda den ur vad som är installerat lokalt.

Primitiv, 64 B indata Genomströmning Notering
SHA-256 (stdlib) 1,50M ops/s baslinje, alltid närvarande
BLAKE3 (pip) 1,44M ops/s i nivå med SHA-256
keccak-256 (pip) 99k ops/s 15x långsammare, flaskhalsen
keccak-256 (Rust-kärna) 1,69M ops/s 17x snabbare än pip

keccak har ingen standardbiblioteksimplementation, och det var det som gjorde den nativa modulen värd att bygga. Gapet är störst vid indata av händelsestorlek eftersom pycryptodomes kostnad där är objektallokering per anrop och inte hashning, precis den storlek ett granskningsspår hashar mest; från ände till ände höjer den nativa kärnan förseglingen av en körning på 1000 händelser från 78 till 491 förseglingar per sekund, ungefär 6x. Avgörande: att aktivera acceleratorn kan inte ändra resultatet: att försegla samma körning med och utan craten ger en bit-identisk rot, en konformanssvit prövar båda vägarna mot vektorerna algoritmförfattarna publicerat, och CI bygger ett enda stabilt-ABI (abi3)-hjul och verifierar den enda artefakten på varje Python från 3.10 till 3.14. Att installera den ändrar hastigheten och inget annat.

Signaturer för en post som varar ett decennium

En signatur på en granskningspost måste hålla så länge posten kan bestridas, och AI-loggningsskyldigheter löper nu mot ett decennium. Om schemat bryts medan en post fortfarande är i sitt lagringsfönster kan en motståndare förfalska och antedatera den, så hela arkivet faller på en gång. Vi benchmarkade Ed25519 mot ECDSA, BLS och NIST:s postkvantfamilj ML-DSA, på genomströmning och storlekar.

Schema Verifiering (ops/s) Signaturstorlek
ECDSA P-256 16 887 ~72 B
ML-DSA-44 (postkvant) 10 725 2420 B
Ed25519 10 375 64 B
BLS12-381 endast referensimpl. 96 B, aggregerar

Två resultat kullkastade antaganden vi burit med oss, och vi behåller båda i protokollet. Ed25519 är inte snabbast att verifiera: ECDSA P-256 mätte ungefär 1,6x snabbare, på optimerad biblioteksassembler. Ed25519 förblir standard på signaturstorlek och på determinism (RFC 8032), som tar bort ECDSA:s nonce-återanvändningsfel som läcker en privat nyckel medan signaturer fortfarande verifierar. Och postkvantsignaturer är inte långsamma att verifiera: ML-DSA-44 gick knappt om Ed25519, med kostnaden i signering (ungefär 13x långsammare) och storlek (ungefär 38x byten). Eftersom ett spår signeras en gång och verifieras i åratal är den dyra sidan den vi gör minst. BLS förkastades på ett krav, inte på en siffra: aggregering fäller ihop många signaturer till 96 byte, men ett aggregat verifierar allt-eller-inget och behöver varje nyckel och meddelande, vilket är oförenligt med att avslöja en gren av ett beslut utan att röja resten.

Omtolkningen som betyder något: signaturer är inte konfidentiella, så "skörda nu, dekryptera senare" gäller inte, och det verkliga hotet är retroaktiv förfalskning inom lagringsfönstret. Att förankra en rot till en publik liggare bevisar när en post fanns oberoende av signaturschemat, så en bruten nyckel kostar förmågan att bevisa vem som förseglade en post men inte när eller vad. Förankring ligger därför på den kritiska vägen och ML-DSA är stegat bakom den, med DSSE-kuvertet hållet algoritmrörligt och algoritmen antecknad per kapsel.

Granskningsspåret per körning

Varje körning är en endast-tillägg-och hashkedjad sekvens av händelser (LLM-anrop, verktygsanrop, hämtningar, sub-agent-spawns, mänskliga godkännanden, skyddsräckesbeslut), med en Merkle-rot över alla och en DSSE-signatur över roten. Bara hashar av in- och utdata lagras, aldrig nyttolasterna, så spåret är integritetsbevarande som standard. Verifieraren returnerar inte bara giltig eller ogiltig; den namnger garantin som brast.

Operation (körning på 102 händelser) Kostnad Skalning
Registrera en händelse ~121 us platt, under 0,01% av ett steg
Försegla, bygga roten 0,15 ms linjär i händelser
Verifiera, räkna om alla löv 9,2 ms linjär, ungefär 60x försegling

Att registrera är mindre än en hundradels procent av ett typiskt agentsteg, som domineras av modellens tur och retur, så overhead är inget skäl att sampla och spåret förblir fullständigt i stället för statistiskt. Verifiering är mycket tyngre än försegling eftersom den räknar om varje löv från innehållet medan förseglingen bara bygger trädet över redan närvarande hashar. Den asymmetrin är rätt för ett granskningsspår, där registrering är konstant och verifiering sällsynt, och den märker ut massverifiering som platsen där den nativa kärnan och parallellism betalar sig härnäst. För att göra garantierna konkreta angriper en demo en förseglad körning på åtta sätt, vart och ett avvisat med ett specifikt skäl:

Attack Avvisad som
Skriva om en modellutdata lövhash-avvikelse
Radera eller infoga en händelse händelseantal-avvikelse
Ordna om händelser kedjebrott
Trunkera körningen kedjehuvud-avvikelse
Nedgradera hashalgoritmen lövhash-avvikelse
Presentera under fel nyckel signaturen verifierar inte

Transparensloggen över körningar

En giltig kapsel per körning kan inte bevisa att mängden körningar är fullständig. En operatör som fäller bort en obekväm körning håller en samling individuellt perfekta kapslar och en historik med ett hål. Vi täpper till det med en enda endast-tillägg-logg av kapselrötter som stöder inklusionsbevis (en körning finns i loggen) och konsistensbevis (loggen vid en tidigare storlek är ett exakt prefix av loggen nu). Konsistens fångar censur: en ombyggd logg med en körning borttagen är internt välformad och varje överlevande kapsel verifierar fortfarande, men den misslyckas med en konsistenskontroll mot den tidigare publicerade roten, och samma kontroll avvisar en antedaterad infogning. Det fungerar bara mot en rot som fästs före manipuleringen, så den roten måste bo någonstans operatören inte kan revidera, som ett publikt ankare.

Vi använder en Merkle Mountain Range i stället för ett vanligt Merkle-träd eftersom ett tillägg bara lägger till noder och aldrig skriver om någon, vilket låter loggen bo på skriv-en-gång- eller objektlagring och håller gamla inklusionsbevis giltiga medan loggen växer. Den egenskapen är nära optimal, inte bara bekväm: ePrint 2025/234 bevisar att varje koncis endast-tillägg-utfästelse måste framkalla ett superlinjärt antal vittnesuppdateringar, och en närliggande MMR-variant når i stort sett gränsen.

Loggstorlek MMR-bygge Vanlig ombyggnad
500 1,3 ms 158 ms (120x)
1000 2,6 ms 618 ms (237x)
2000 5,2 ms 2491 ms (483x)

Tillägg förblir platt medan loggen växer (ungefär 390k tillägg per sekund vid både 100 och 10 000 poster) medan ombyggnad av ett vanligt träd faller från 8176 till 84 rötter per sekund över samma intervall, den kvadratiska-mot-linearitmiska signaturen. Lagringen lägger sig på 2,00 lagrade hashar per post och inklusionsbevis växer bara från 7 till 17 steg mellan 100 och 100 000 poster. Där vi förlorar: våra konsistensbevis är O(log^2 n), och bär en väg per topp i den gamla loggen i stället för det uppnåeliga O(log n), några kilobyte vid 100 000 poster och en medveten avvägning för en konstruktion som en granskare kan kontrollera genom att läsa.

Gränser vi inte överskrider

Hashning ger manipuleringsröjande, inte manipuleringssäkerhet. En motståndare som bygger om en körning och räknar om dess rot producerar en självkonsistent post, eftersom en Merkle-rot bevisar att händelserna stämmer med roten, inte att roten är den som publicerades. Signaturen fångar det, och det publika ankaret bevisar tiden. Transparensloggen vilar i stort på samma antagande: den upptäcker en raderad körning bara relativt en rot som fästs före raderingen. Vi anger dessa gränser i produkten och dess demor, och ett test hävdar att förfalskningsdemon fortsätter visa attacken vi inte stoppar, så att det ärliga fallet inte kan försvinna i tysthet.

Reproducerbarhet

Varje siffra här kommer från en incheckad benchmark som körs om med ett kommando, jämte testerna, konformanssviterna mellan språk och mellan implementationer, och CI-matrisen som motionerar Python 3.10 till 3.14 med och utan den nativa kärnan. Metodiken, den råa JSON:en och beslutsnotaterna (inklusive dimensionerna där varje val förlorar) finns i förvaret, för ett lager som gör AI granskningsbar måste självt vara granskningsbart.