./research / verifiable-audit-layer

Weryfikowalna warstwa audytu dla SI.

Odporna na manipulacje warstwa audytu dla inferencji modeli i wykonania agentów: kanoniczna serializacja, haszowanie z separacją domen, podpisy DSSE, Merkle DAG na przebieg oraz międzyprzebiegowy Merkle Mountain Range, który dowodzi, że żaden przebieg nie został usunięty. Każdy prymityw wybrano z bezpośredniego benchmarku wobec realnych alternatyw, z surowymi danymi w repozytorium. Ta notatka to projekt i stojące za nim eksperymenty.

audytkryptografiabenchmarkishipped

Model zagrożenia

Zakładamy przeciwnika, który kontroluje magazyn i może przepisywać rekordy po fakcie, wliczając operatora, który je wytworzył. Celem jest, by każda edycja zapieczętowanego przebiegu była wykrywalna, by rekord pozostawał weryfikowalny przez okres przechowywania liczony w latach, i by weryfikacja nie ujawniała żadnych prywatnych danych wejściowych. Dwie własności są przez cały czas trzymane osobno: integralność pojedynczego przebiegu oraz kompletność zbioru przebiegów. Pokwitowanie na przebieg daje pierwszą i nic nie mówi o drugiej, więc obie projektuje się oddzielnie.

Każdy wybór poniżej podjęto tak samo. Nie twierdzimy, że dany prymityw jest najlepszy; porównujemy go z wiarygodnymi alternatywami na tej samej maszynie i zapisujemy surowe dane, tak że każda liczba tutaj odtwarza się jedną komendą. Czasy to najszybszy z kilku przebiegów po odrzuceniu rozgrzewki, z zachowaną średnią i odchyleniem standardowym, by niestabilny pomiar był oznaczony, a nie ukryty. Liczby pochodzą z jednego hosta AMD64 na CPython 3.14 i są znaczące jako stosunki, nie jako wartości bezwzględne.

Eksperymenty i wyniki

Każdy kamień milowy był bezpośrednim testem wobec realnych alternatyw, nie pojedynczym pomiarem. Przegląd jest poniżej; szczegółowe wyniki każdego są w kolejnych sekcjach.

Kamień milowy Testowano wobec Wynik
Forma kanoniczna naiwny JSON, CBOR JCS, identyczny bajt w bajt, ~3x kosztu
Haszowanie SHA-256, BLAKE3, keccak-256 keccak natywnie 17x, te same bajty
Rdzeń natywny backendy pip vs Rust pieczętowanie 6x, korzeń identyczny bajt w bajt
Podpis ECDSA, BLS, ML-DSA Ed25519, 64 B, deterministyczny
Postkwantowy ML-DSA 44 / 65 / 87 etapowo, weryfikacja na równi, 38x rozmiaru
Ślad na przebieg 8 ataków fałszerstwa, narzut wszystkie złapane, ~121 us / zdarzenie
Log międzyprzebiegowy zwykła przebudowa Merkle MMR, płaskie dołączanie, 483x

Kanoniczna serializacja

Kapsuła musi haszować się do tych samych bajtów w naszym rdzeniu Python, w naszym kliencie TypeScript i we własnym narzędziu audytora, inaczej ważny rekord nie przejdzie weryfikacji. Używamy RFC 8785 (JSON Canonicalization Scheme), który ustala formatowanie liczb, kolejność kluczy według jednostki kodu UTF-16 i odrzucanie wartości nieskończonych. Przetestowaliśmy go wobec JSON-a z naiwnie sortowanymi kluczami i wobec CBOR.

Podejście Python vs Node Koszt
Naiwnie sortowany JSON rozjeżdża się na Unicode i liczbach odniesienie
RFC 8785 JCS identyczny bajt w bajt około 3x przy serializacji
CBOR binarny, do odczytu potrzebuje dekodera zwarty

Naiwny JSON rozjeżdża się między środowiskami uruchomieniowymi na kolejności Unicode i formatowaniu liczb, przez co cicho psuje weryfikację między językami. JCS jest identyczny bajt w bajt w obu, przy około 3x czasie serializacji, płaconym raz na hasz i pomijalnym wobec haszowania i pracy sieciowej wokół. Trzymamy dokładnie jedną jego implementację. Gdy nasz moduł natywny przyniósł własny serializator JSON, usunęliśmy go, bo drugi kanonikalizator, który zgadza się na zwykłych wejściach, a rozjeżdża się na przypadkach brzegowych, jakie może wybrać przeciwnik, jest gorszy niż żaden.

Haszowanie i rdzeń natywny

Liście haszuje się BLAKE3, a węzły Merkle keccak-256, oba z separacją domen RFC 6962 (odrębne prefiksy dla liści i węzłów wewnętrznych), by zamknąć klasę drugiego przeciwobrazu, w której węzeł wewnętrzny podaje się za liść. keccak-256 to nie SHA3-256; oba dzielą permutację, lecz różnią się jednym bajtem dopełnienia, a weryfikator on-chain mówi keccak, więc ciche podstawienie sprawiłoby, że każdy korzeń zawiedzie on-chain. Warstwa haszowania oblicza zatem żądany algorytm albo rzuca błąd, nigdy sobowtóra, i zapisuje algorytm w każdej kapsule, by weryfikator odtwarzał dokładną funkcję, a nie wnioskował ją z tego, co zainstalowano lokalnie.

Prymityw, wejście 64 B Przepustowość Uwaga
SHA-256 (stdlib) 1,50M ops/s odniesienie, zawsze obecny
BLAKE3 (pip) 1,44M ops/s na równi z SHA-256
keccak-256 (pip) 99k ops/s 15x wolniej, wąskie gardło
keccak-256 (rdzeń Rust) 1,69M ops/s 17x szybciej niż pip

keccak nie ma implementacji w bibliotece standardowej, co uczyniło moduł natywny wartym zbudowania. Przepaść jest największa przy wejściach wielkości zdarzenia, bo koszt pycryptodome jest tam alokacją obiektów na wywołanie, a nie haszowaniem, dokładnie tym rozmiarem, który ślad audytu haszuje najczęściej; od początku do końca rdzeń natywny podnosi pieczętowanie przebiegu 1000 zdarzeń z 78 do 491 pieczętowań na sekundę, około 6x. Kluczowe: włączenie akceleratora nie może zmienić wyniku: zapieczętowanie tego samego przebiegu z crate'em i bez daje korzeń identyczny bit w bit, zestaw zgodności sprawdza obie ścieżki wobec wektorów opublikowanych przez autorów algorytmu, a CI buduje jedno koło o stabilnym ABI (abi3) i weryfikuje ten jeden artefakt na każdym Pythonie od 3.10 do 3.14. Instalacja zmienia szybkość i nic więcej.

Podpisy dla rekordu na dekadę

Podpis na rekordzie audytu musi utrzymać się tak długo, jak długo rekord można zakwestionować, a obowiązki logowania SI zmierzają już ku dekadzie. Jeśli schemat pęknie, gdy rekord jest jeszcze w oknie przechowywania, przeciwnik może go sfałszować i antydatować, więc całe archiwum upada naraz. Porównaliśmy Ed25519 wobec ECDSA, BLS i postkwantowej rodziny NIST ML-DSA, na przepustowości i rozmiarach.

Schemat Weryfikacja (ops/s) Rozmiar podpisu
ECDSA P-256 16 887 ~72 B
ML-DSA-44 (postkwantowy) 10 725 2420 B
Ed25519 10 375 64 B
BLS12-381 tylko impl. referencyjna 96 B, agreguje

Dwa wyniki obaliły założenia, które ze sobą przynieśliśmy, i oba trzymamy w aktach. Ed25519 nie jest najszybszy do weryfikacji: ECDSA P-256 zmierzył około 1,6x szybciej, na zoptymalizowanym asemblerze biblioteki. Ed25519 pozostaje domyślny ze względu na rozmiar podpisu i determinizm (RFC 8032), który usuwa awarię ponownego użycia nonce w ECDSA, wyciekającą klucz prywatny, podczas gdy podpisy nadal się weryfikują. A podpisy postkwantowe nie są wolne w weryfikacji: ML-DSA-44 minimalnie wyprzedził Ed25519, a jego koszt leży w podpisywaniu (około 13x wolniej) i w rozmiarze (około 38x bajtów). Ponieważ ślad podpisuje się raz i weryfikuje przez lata, kosztowna strona to ta, którą robimy najrzadziej. BLS odrzucono na wymaganiu, nie na liczbie: agregacja składa wiele podpisów w 96 bajtów, ale agregat weryfikuje się wszystko-albo-nic i potrzebuje każdego klucza i komunikatu, co jest niezgodne z ujawnieniem jednej gałęzi decyzji bez odsłaniania reszty.

Przeformułowanie, które ma znaczenie: podpisy nie są poufne, więc "zbieraj teraz, odszyfruj później" nie ma zastosowania, a prawdziwym zagrożeniem jest retroaktywne fałszerstwo w oknie przechowywania. Zakotwiczenie korzenia w publicznym rejestrze dowodzi, kiedy rekord istniał niezależnie od schematu podpisu, więc złamany klucz kosztuje zdolność udowodnienia, kto zapieczętował rekord, ale nie kiedy ani co. Zakotwiczenie jest zatem na ścieżce krytycznej, a ML-DSA ustawiony za nim etapowo, przy czym koperta DSSE pozostaje zwinna algorytmicznie, a algorytm zapisywany na kapsułę.

Ślad audytu na przebieg

Każdy przebieg to sekwencja zdarzeń tylko-do-dopisywania i połączona haszem (wywołania LLM, wywołania narzędzi, pobrania, tworzenie podagentów, zatwierdzenia ludzkie, decyzje barierek), z korzeniem Merkle nad wszystkimi i podpisem DSSE nad korzeniem. Przechowuje się tylko hasze wejść i wyjść, nigdy ładunki, więc ślad domyślnie chroni prywatność. Weryfikator nie zwraca po prostu ważny lub nieważny; nazywa gwarancję, która pękła.

Operacja (przebieg 102 zdarzeń) Koszt Skalowanie
Zapisać jedno zdarzenie ~121 us płasko, poniżej 0,01% kroku
Zapieczętować, zbudować korzeń 0,15 ms liniowo względem zdarzeń
Zweryfikować, przeliczyć wszystkie liście 9,2 ms liniowo, około 60x pieczętowania

Zapis to mniej niż setna część procenta typowego kroku agenta, zdominowanego przez rundę do modelu, więc narzut nie jest powodem do próbkowania, a ślad pozostaje kompletny, a nie statystyczny. Weryfikacja jest o wiele cięższa od pieczętowania, bo przelicza każdy liść z treści, podczas gdy pieczętowanie buduje drzewo tylko nad już obecnymi haszami. Ta asymetria jest właściwa dla śladu audytu, gdzie zapis jest stały, a weryfikacja rzadka, i wskazuje masową ponowną weryfikację jako miejsce, gdzie rdzeń natywny i równoległość opłacą się w następnej kolejności. By uczynić gwarancje konkretnymi, demo atakuje zapieczętowany przebieg na osiem sposobów, każdy odrzucony z konkretnym powodem:

Atak Odrzucony jako
Przepisać wyjście modelu niezgodność haszu liścia
Usunąć lub wstawić zdarzenie niezgodność liczby zdarzeń
Zmienić kolejność zdarzeń zerwanie łańcucha
Obciąć przebieg niezgodność głowy łańcucha
Obniżyć algorytm haszu niezgodność haszu liścia
Przedstawić pod złym kluczem podpis się nie weryfikuje

Międzyprzebiegowy log przejrzystości

Ważna kapsuła na przebieg nie może dowieść, że zbiór przebiegów jest kompletny. Operator, który porzuca niewygodny przebieg, trzyma kolekcję indywidualnie doskonałych kapsuł i historię z dziurą. Domykamy to jednym logiem tylko-do-dopisywania korzeni kapsuł, wspierającym dowody inkluzji (przebieg jest w logu) i dowody spójności (log o wcześniejszym rozmiarze jest dokładnym prefiksem logu obecnie). Spójność łapie cenzurę: przebudowany log z usuniętym przebiegiem jest wewnętrznie poprawny i każda ocalała kapsuła nadal się weryfikuje, ale nie przechodzi kontroli spójności wobec wcześniej opublikowanego korzenia, a ta sama kontrola odrzuca antydatowane wstawienie. Działa to tylko wobec korzenia zatwierdzonego przed manipulacją, więc ten korzeń musi żyć gdzieś, czego operator nie może zrewidować, jak publiczna kotwica.

Używamy Merkle Mountain Range zamiast zwykłego drzewa Merkle, bo dopisanie tylko dodaje węzły i nigdy żadnego nie przepisuje, co pozwala logowi żyć na magazynie zapisu-jednokrotnego lub obiektowym i utrzymuje dawne dowody inkluzji ważnymi w miarę wzrostu logu. Ta własność jest niemal optymalna, nie tylko wygodna: ePrint 2025/234 dowodzi, że każde zwięzłe zobowiązanie tylko-do-dopisywania musi wymuszać superliniową liczbę aktualizacji świadków, a bliski wariant MMR zasadniczo osiąga granicę.

Rozmiar logu Budowa MMR Zwykła przebudowa
500 1,3 ms 158 ms (120x)
1000 2,6 ms 618 ms (237x)
2000 5,2 ms 2491 ms (483x)

Dopisywanie pozostaje płaskie w miarę wzrostu logu (około 390k dopisań na sekundę zarówno przy 100, jak i 10 000 wpisach), podczas gdy przebudowa zwykłego drzewa spada z 8176 do 84 korzeni na sekundę w tym samym zakresie, sygnatura kwadratowa-kontra-linearytmiczna. Magazyn ustala się na 2,00 przechowywanego haszu na wpis, a dowody inkluzji rosną tylko z 7 do 17 kroków między 100 a 100 000 wpisów. Gdzie tracimy: nasze dowody spójności są O(log^2 n), niosąc jedną ścieżkę na szczyt starego logu zamiast osiągalnego O(log n), kilka kilobajtów przy 100 000 wpisów i świadomy kompromis na rzecz konstrukcji, którą recenzent może sprawdzić czytając.

Granice, których nie przekraczamy

Haszowanie daje wykrywalność manipulacji, nie odporność na nią. Przeciwnik, który przebudowuje przebieg i przelicza jego korzeń, wytwarza rekord samospójny, bo korzeń Merkle dowodzi, że zdarzenia pasują do korzenia, nie że korzeń jest tym, który opublikowano. Podpis to łapie, a publiczna kotwica dowodzi czasu. Log przejrzystości spoczywa w skali na tym samym założeniu: wykrywa usunięty przebieg tylko względem korzenia zatwierdzonego przed usunięciem. Deklarujemy te granice w produkcie i jego demach, a test zapewnia, że demo fałszerstwa wciąż pokazuje atak, którego nie zatrzymujemy, tak by uczciwy przypadek nie mógł po cichu zniknąć.

Odtwarzalność

Każda liczba tutaj pochodzi z zapisanego benchmarku, który przebiega ponownie jedną komendą, obok testów, zestawów zgodności między językami i między implementacjami oraz macierzy CI ćwiczącej Pythona od 3.10 do 3.14 z rdzeniem natywnym i bez niego. Metodologia, surowy JSON i notatki decyzyjne (w tym wymiary, w których każdy wybór przegrywa) są w repozytorium, bo warstwa, która czyni SI audytowalną, sama musi być audytowalna.