./research / verifiable-audit-layer

Uno strato di audit verificabile per l'IA.

Uno strato di audit a prova di manomissione per l'inferenza dei modelli e l'esecuzione degli agenti: serializzazione canonica, hashing con separazione di dominio, firme DSSE, un DAG di Merkle per esecuzione e una Merkle Mountain Range tra esecuzioni che prova che nessuna esecuzione è stata cancellata. Ogni primitiva è stata scelta da un benchmark diretto contro le vere alternative, con i dati grezzi versionati. Questa nota è la progettazione e gli esperimenti che le stanno dietro.

auditcrittografiabenchmarkshipped

Modello di minaccia

Assumiamo un avversario che controlla l'archiviazione e può riscrivere i record a posteriori, incluso l'operatore che li ha prodotti. L'obiettivo è che ogni modifica a un'esecuzione sigillata sia rilevabile, che il record resti verificabile per un periodo di conservazione misurato in anni, e che la verifica non esponga alcun input privato. Due proprietà restano distinte per tutto il tempo: l'integrità di una singola esecuzione e la completezza dell'insieme delle esecuzioni. Una ricevuta per esecuzione dà la prima e non dice nulla sulla seconda, quindi entrambe sono progettate separatamente.

Ogni scelta qui sotto è stata fatta allo stesso modo. Non affermiamo che una primitiva sia la migliore; la confrontiamo con le alternative credibili sulla stessa macchina e versioniamo i dati grezzi, così ogni cifra qui si riproduce con un comando. I tempi sono il più veloce di più esecuzioni dopo aver scartato il riscaldamento, mantenendo media e deviazione standard perché una misura instabile sia segnalata anziché nascosta. I numeri provengono da un singolo host AMD64 su CPython 3.14 e sono significativi come rapporti, non come valori assoluti.

Esperimenti ed esiti

Ogni traguardo è stato un test diretto contro le vere alternative, non una singola misura. La panoramica è sotto; i risultati dettagliati di ciascuno sono nelle sezioni seguenti.

Traguardo Testato contro Esito
Forma canonica JSON ingenuo, CBOR JCS, identico byte per byte, ~3x di costo
Hashing SHA-256, BLAKE3, keccak-256 keccak nativo 17x, stessi byte
Nucleo nativo backend pip vs Rust sigillatura 6x, radice identica byte per byte
Firma ECDSA, BLS, ML-DSA Ed25519, 64 B, deterministica
Post-quantistico ML-DSA 44 / 65 / 87 a fasi, verifica alla pari, 38x di dimensione
Traccia per esecuzione 8 attacchi di falsificazione, overhead tutti rilevati, ~121 us / evento
Log tra esecuzioni ricostruzione Merkle semplice MMR, append piatto, 483x

Serializzazione canonica

Una capsula deve produrre lo stesso hash di byte nel nostro nucleo Python, nel nostro client TypeScript e nello strumento proprio di un revisore, altrimenti un record valido non passa la verifica. Usiamo RFC 8785 (JSON Canonicalization Scheme), che fissa la formattazione dei numeri, l'ordine delle chiavi per unità di codice UTF-16 e il rifiuto dei valori non finiti. L'abbiamo testato contro JSON a chiavi ordinate in modo ingenuo e contro CBOR.

Approccio Python vs Node Costo
JSON ordinato ingenuo diverge su Unicode e numeri riferimento
RFC 8785 JCS identico byte per byte circa 3x nel serializzare
CBOR binario, serve un decoder per leggerlo compatto

Il JSON ingenuo diverge tra runtime sull'ordinamento Unicode e sulla formattazione dei numeri, quindi rompe in silenzio la verifica tra linguaggi. JCS è identico byte per byte in entrambi, a circa 3x il tempo di serializzazione, pagato una volta per hash e trascurabile rispetto all'hashing e al lavoro di rete circostante. Ne teniamo esattamente un'implementazione. Quando il nostro modulo nativo ha portato il proprio serializzatore JSON, l'abbiamo rimosso, perché un secondo canonicalizzatore che concorda sugli input ordinari e diverge sui casi limite che un avversario può scegliere è peggio di nessuno.

Hashing e il nucleo nativo

Le foglie sono hashate con BLAKE3 e i nodi di Merkle con keccak-256, entrambi con separazione di dominio RFC 6962 (prefissi distinti per foglie e nodi interni) per chiudere la classe di seconda preimmagine in cui un nodo interno è presentato come foglia. keccak-256 non è SHA3-256; i due condividono una permutazione ma differiscono di un byte di padding, e il verificatore on-chain parla keccak, quindi una sostituzione silenziosa farebbe fallire ogni radice on-chain. Lo strato di hashing calcola perciò l'algoritmo richiesto o solleva un errore, mai un sosia, e registra l'algoritmo in ogni capsula così che un verificatore riproduca la funzione esatta invece di dedurla da ciò che è installato localmente.

Primitiva, input da 64 B Throughput Nota
SHA-256 (stdlib) 1,50M ops/s riferimento, sempre presente
BLAKE3 (pip) 1,44M ops/s alla pari con SHA-256
keccak-256 (pip) 99k ops/s 15x più lento, il collo di bottiglia
keccak-256 (nucleo Rust) 1,69M ops/s 17x più veloce di pip

keccak non ha un'implementazione nella libreria standard, ed è ciò che ha reso il modulo nativo degno di essere costruito. Il divario è massimo su input di dimensione evento perché lì il costo di pycryptodome è l'allocazione di oggetti per chiamata e non l'hashing, esattamente la dimensione che una traccia di audit hasha di più; da capo a fondo, il nucleo nativo porta la sigillatura di un'esecuzione di 1000 eventi da 78 a 491 sigillature al secondo, circa 6x. Fondamentale: abilitare l'acceleratore non può cambiare il risultato: sigillare la stessa esecuzione con e senza la crate produce una radice identica bit per bit, una suite di conformità controlla entrambi i percorsi contro i vettori pubblicati dagli autori dell'algoritmo, e la CI costruisce una singola wheel ad ABI stabile (abi3) e verifica quell'unico artefatto su ogni Python da 3.10 a 3.14. Installarlo cambia la velocità e nient'altro.

Firme per un record decennale

Una firma su un record di audit deve reggere per tutto il tempo in cui il record può essere contestato, e gli obblighi di logging dell'IA si spingono ormai verso il decennio. Se lo schema si rompe mentre un record è ancora nella sua finestra di conservazione, un avversario può falsificarlo e retrodatarlo, quindi l'intero archivio fallisce in un colpo. Abbiamo valutato Ed25519 contro ECDSA, BLS e la famiglia post-quantistica NIST ML-DSA, su throughput e dimensioni.

Schema Verifica (ops/s) Dimensione firma
ECDSA P-256 16 887 ~72 B
ML-DSA-44 (post-quantistico) 10 725 2420 B
Ed25519 10 375 64 B
BLS12-381 solo impl. di riferimento 96 B, aggrega

Due risultati hanno ribaltato assunti che ci portavamo dietro, e li teniamo entrambi agli atti. Ed25519 non è il più veloce da verificare: ECDSA P-256 ha misurato circa 1,6x più veloce, su assembly di libreria ottimizzato. Ed25519 resta il default per dimensione della firma e per determinismo (RFC 8032), che elimina il guasto da riuso di nonce di ECDSA che fa trapelare una chiave privata mentre le firme ancora verificano. E le firme post-quantistiche non sono lente da verificare: ML-DSA-44 ha superato di poco Ed25519, con il costo nella firma (circa 13x più lento) e nella dimensione (circa 38x i byte). Poiché una traccia è firmata una volta e verificata per anni, il lato costoso è quello che facciamo di meno. BLS è stato scartato su un requisito, non su un numero: l'aggregazione collassa molte firme in 96 byte, ma un aggregato verifica tutto-o-niente e richiede ogni chiave e messaggio, il che è incompatibile con il divulgare un ramo di una decisione senza rivelare il resto.

Il riformulare che conta: le firme non sono riservate, quindi "raccogli ora, decifra dopo" non si applica, e la minaccia reale è la falsificazione retroattiva entro la finestra di conservazione. Ancorare una radice a un registro pubblico prova quando un record è esistito indipendentemente dallo schema di firma, quindi una chiave rotta costa la capacità di provare chi ha sigillato un record ma non quando o cosa. L'ancoraggio è perciò sul percorso critico e ML-DSA è messo in fila dietro di esso, con la busta DSSE mantenuta agile rispetto all'algoritmo e l'algoritmo registrato per capsula.

La traccia di audit per esecuzione

Ogni esecuzione è una sequenza di eventi solo-append e concatenata per hash (chiamate LLM, chiamate a strumenti, recuperi, spawn di sub-agenti, approvazioni umane, decisioni di guardrail), con una radice di Merkle su tutti e una firma DSSE sulla radice. Sono memorizzati solo hash di input e output, mai i payload, quindi la traccia preserva la privacy per default. Il verificatore non restituisce solo valido o non valido; nomina la garanzia che si è rotta.

Operazione (esecuzione di 102 eventi) Costo Scalabilità
Registrare un evento ~121 us piatto, sotto lo 0,01% di un passo
Sigillare, costruire la radice 0,15 ms lineare negli eventi
Verificare, ricalcolare tutte le foglie 9,2 ms lineare, circa 60x la sigillatura

Registrare è meno di un centesimo di punto percentuale di un tipico passo di agente, dominato dal round-trip del modello, quindi l'overhead non è motivo per campionare e la traccia resta completa anziché statistica. La verifica è molto più pesante della sigillatura perché ricalcola ogni foglia dal contenuto mentre la sigillatura costruisce solo l'albero su hash già presenti. Quell'asimmetria è corretta per una traccia di audit, dove registrare è costante e verificare è raro, e segna la riverifica in massa come il punto in cui il nucleo nativo e il parallelismo rendono dopo. Per rendere concrete le garanzie, una demo attacca un'esecuzione sigillata in otto modi, ciascuno respinto con una ragione specifica:

Attacco Respinto come
Riscrivere un output del modello discordanza di hash di foglia
Cancellare o inserire un evento discordanza del conteggio eventi
Riordinare gli eventi rottura della catena
Troncare l'esecuzione discordanza di testa della catena
Declassare l'algoritmo di hash discordanza di hash di foglia
Presentare sotto la chiave sbagliata la firma non verifica

Il log di trasparenza tra esecuzioni

Una capsula per esecuzione valida non può provare che l'insieme delle esecuzioni sia completo. Un operatore che scarta un'esecuzione scomoda detiene una collezione di capsule singolarmente perfette e una storia con un buco. Lo chiudiamo con un singolo log solo-append di radici di capsula che supporta prove di inclusione (un'esecuzione è nel log) e prove di consistenza (il log a una dimensione precedente è un prefisso esatto del log attuale). La consistenza coglie la censura: un log ricostruito con un'esecuzione rimossa è internamente ben formato e ogni capsula superstite verifica ancora, ma fallisce un controllo di consistenza contro la radice pubblicata prima, e lo stesso controllo respinge un inserimento retrodatato. Questo funziona solo contro una radice fissata prima della manomissione, quindi quella radice deve vivere da qualche parte che l'operatore non possa rivedere, come un'ancora pubblica.

Usiamo una Merkle Mountain Range anziché un albero di Merkle semplice perché un append aggiunge solo nodi e non ne riscrive mai uno, il che lascia vivere il log su storage write-once o a oggetti e mantiene valide le vecchie prove di inclusione mentre il log cresce. Quella proprietà è quasi ottimale, non solo comoda: ePrint 2025/234 prova che ogni impegno succinto solo-append deve indurre un numero superlineare di aggiornamenti di testimone, e una variante vicina di MMR sostanzialmente raggiunge il limite.

Dimensione del log Costruzione MMR Ricostruzione semplice
500 1,3 ms 158 ms (120x)
1000 2,6 ms 618 ms (237x)
2000 5,2 ms 2491 ms (483x)

L'append resta piatto mentre il log cresce (circa 390k append al secondo sia a 100 sia a 10 000 voci) mentre ricostruire un albero semplice cala da 8176 a 84 radici al secondo nello stesso intervallo, la firma quadratica-contro-linearitmica. Lo storage si assesta a 2,00 hash memorizzati per voce e le prove di inclusione crescono solo da 7 a 17 passi tra 100 e 100 000 voci. Dove perdiamo: le nostre prove di consistenza sono O(log^2 n), portando un percorso per picco del vecchio log invece dell'O(log n) raggiungibile, pochi kilobyte a 100 000 voci e un compromesso deliberato per una costruzione che un revisore può controllare leggendola.

Limiti che non superiamo

L'hashing dà evidenza di manomissione, non immunità dalla manomissione. Un avversario che ricostruisce un'esecuzione e ne ricalcola la radice produce un record auto-consistente, perché una radice di Merkle prova che gli eventi corrispondono alla radice, non che la radice sia quella pubblicata. La firma coglie ciò, e l'ancora pubblica prova il momento. Il log di trasparenza poggia in grande sulla stessa assunzione: rileva un'esecuzione cancellata solo rispetto a una radice fissata prima della cancellazione. Dichiariamo questi confini nel prodotto e nelle sue demo, e un test asserisce che la demo di falsificazione continui a mostrare l'attacco che non fermiamo, così che il caso onesto non possa sparire in silenzio.

Riproducibilità

Ogni cifra qui viene da un benchmark versionato che si riesegue con un comando, accanto ai test, alle suite di conformità tra linguaggi e tra implementazioni, e alla matrice CI che esercita Python 3.10 fino a 3.14 con e senza il nucleo nativo. La metodologia, il JSON grezzo e le note di decisione (incluse le dimensioni in cui ogni scelta perde) sono nel repository, perché uno strato che rende l'IA auditabile deve essere auditabile esso stesso.