./research / verifiable-audit-layer

Una capa de auditoría verificable para la IA.

Una capa de auditoría a prueba de manipulaciones para la inferencia de modelos y la ejecución de agentes: serialización canónica, hashing con separación de dominios, firmas DSSE, un DAG de Merkle por ejecución y un Merkle Mountain Range entre ejecuciones que prueba que no se borró ninguna. Cada primitiva se eligió a partir de un benchmark directo frente a las alternativas reales, con los datos en bruto incluidos en el repositorio. Esta nota es el diseño y los experimentos que hay detrás.

auditoríacriptografíabenchmarksshipped

Modelo de amenaza

Suponemos un adversario que controla el almacenamiento y puede reescribir registros a posteriori, incluido el propio operador que los produjo. El objetivo es que cualquier edición de una ejecución sellada sea detectable, que el registro siga siendo verificable durante un periodo de retención medido en años, y que la verificación no exponga ninguna entrada privada. Se mantienen dos propiedades distintas en todo momento: la integridad de una sola ejecución y la completitud del conjunto de ejecuciones. Un recibo por ejecución da la primera y no dice nada sobre la segunda, así que ambas se diseñan por separado.

Cada decisión de abajo se tomó del mismo modo. No afirmamos que una primitiva sea la mejor; la comparamos con las alternativas creíbles en la misma máquina e incluimos los datos en bruto, de modo que cualquier cifra aquí se reproduce con un solo comando. Los tiempos son el más rápido de varias ejecuciones tras descartar el calentamiento, conservando la media y la desviación estándar para que una medición inestable quede señalada en lugar de oculta. Los números provienen de un único host AMD64 en CPython 3.14 y son significativos como proporciones, no como valores absolutos.

Experimentos y resultados

Cada hito fue una prueba directa frente a las alternativas reales, no una única medición. El resumen está abajo; los resultados detallados de cada uno están en las secciones que siguen.

Hito Comparado con Resultado
Forma canónica JSON ingenuo, CBOR JCS, idéntico byte a byte, ~3x de coste
Hashing SHA-256, BLAKE3, keccak-256 keccak nativo 17x, mismos bytes
Núcleo nativo backends de pip vs Rust sellado 6x, raíz idéntica byte a byte
Firma ECDSA, BLS, ML-DSA Ed25519, 64 B, determinista
Poscuántico ML-DSA 44 / 65 / 87 por etapas, verificación a la par, 38x de tamaño
Traza por ejecución 8 ataques de falsificación, sobrecarga todos detectados, ~121 us / evento
Registro entre ejecuciones reconstrucción de Merkle simple MMR, anexado plano, 483x

Serialización canónica

Una cápsula debe producir el mismo hash de bytes en nuestro núcleo de Python, en nuestro cliente de TypeScript y en la propia herramienta de un auditor, o un registro válido no llega a verificarse. Usamos RFC 8785 (JSON Canonicalization Scheme), que fija el formato de los números, el orden de las claves por unidad de código UTF-16 y el rechazo de valores no finitos. Lo probamos frente a JSON con claves ordenadas de forma ingenua y frente a CBOR.

Enfoque Python vs Node Coste
JSON ingenuo ordenado diverge en Unicode y números referencia
RFC 8785 JCS idéntico byte a byte unas 3x al serializar
CBOR binario, necesita un decodificador para leerse compacto

El JSON ingenuo diverge entre entornos de ejecución en el orden Unicode y el formato de los números, así que rompe en silencio la verificación entre lenguajes. JCS es idéntico byte a byte en ambos, a aproximadamente 3x el tiempo de serialización, que se paga una vez por hash y es insignificante frente al hashing y el trabajo de red que lo rodean. Mantenemos exactamente una implementación. Cuando nuestro módulo nativo trajo su propio serializador de JSON, lo quitamos, porque un segundo canonicalizador que coincide en entradas ordinarias y diverge en los casos límite que un adversario puede elegir es peor que ninguno.

Hashing y el núcleo nativo

Las hojas se hashean con BLAKE3 y los nodos de Merkle con keccak-256, ambos con separación de dominios RFC 6962 (prefijos distintos para hojas y nodos interiores) para cerrar la clase de segunda preimagen donde un nodo interior se presenta como una hoja. keccak-256 no es SHA3-256; los dos comparten una permutación pero difieren en un byte de relleno, y el verificador on-chain habla keccak, así que una sustitución silenciosa haría que cada raíz fallara on-chain. Por eso la capa de hashing calcula el algoritmo solicitado o lanza un error, nunca un parecido, y registra el algoritmo en cada cápsula para que un verificador reproduzca la función exacta en lugar de inferirla de lo que haya instalado localmente.

Primitiva, entrada de 64 B Rendimiento Nota
SHA-256 (stdlib) 1,50M ops/s referencia, siempre presente
BLAKE3 (pip) 1,44M ops/s a la par con SHA-256
keccak-256 (pip) 99k ops/s 15x más lento, el cuello de botella
keccak-256 (núcleo en Rust) 1,69M ops/s 17x más rápido que pip

keccak no tiene implementación en la biblioteca estándar, que es lo que hizo que valiera la pena construir el módulo nativo. La brecha es mayor con entradas del tamaño de un evento porque el coste de pycryptodome ahí es la asignación de objetos por llamada y no el hashing, justo el tamaño que una traza de auditoría hashea más. De extremo a extremo, el núcleo nativo reduce el sellado de una ejecución de 1000 eventos de 78 a 491 sellados por segundo, unas 6x. Y algo crucial: habilitar el acelerador no puede cambiar el resultado: sellar la misma ejecución con y sin el crate da una raíz idéntica bit a bit, una suite de conformidad comprueba ambos caminos contra los vectores publicados por los autores del algoritmo, y CI compila una única rueda de ABI estable (abi3) y verifica ese único artefacto en cada Python de 3.10 a 3.14. Instalarlo cambia la velocidad y nada más.

Firmas para un registro de una década

Una firma sobre un registro de auditoría tiene que aguantar tanto tiempo como pueda disputarse el registro, y las obligaciones de registro de IA ya se acercan a la década. Si el esquema se rompe mientras un registro sigue en su ventana de retención, un adversario puede falsificarlo y antedatarlo, así que todo el archivo falla de golpe. Comparamos Ed25519 con ECDSA, BLS y la familia poscuántica del NIST ML-DSA, en rendimiento y tamaños.

Esquema Verificación (ops/s) Tamaño de firma
ECDSA P-256 16 887 ~72 B
ML-DSA-44 (poscuántico) 10 725 2420 B
Ed25519 10 375 64 B
BLS12-381 solo impl. de referencia 96 B, agrega

Dos resultados desmontaron supuestos que traíamos, y dejamos ambos en el registro. Ed25519 no es el más rápido de verificar: ECDSA P-256 midió unas 1,6x más rápido, sobre ensamblador de biblioteca optimizado. Ed25519 sigue siendo el predeterminado por el tamaño de firma y por el determinismo (RFC 8032), que elimina el fallo de reutilización de nonce de ECDSA que filtra una clave privada mientras las firmas aún verifican. Y las firmas poscuánticas no son lentas de verificar: ML-DSA-44 superó por poco a Ed25519, y su coste está en firmar (unas 13x más lento) y en el tamaño (unas 38x los bytes). Como una traza se firma una vez y se verifica durante años, el lado caro es el que menos hacemos. BLS se descartó por un requisito, no por un número: la agregación colapsa muchas firmas en 96 bytes, pero un agregado verifica todo o nada y necesita cada clave y mensaje, lo que es incompatible con revelar una rama de una decisión sin revelar el resto.

El replanteamiento que importa: las firmas no son confidenciales, así que "cosechar ahora, descifrar después" no aplica, y la amenaza real es la falsificación retroactiva dentro de la ventana de retención. Anclar una raíz a un libro mayor público prueba cuándo existió un registro con independencia del esquema de firma, así que una clave rota cuesta la capacidad de probar quién selló un registro pero no cuándo ni qué. Por eso el anclaje está en la ruta crítica y ML-DSA está escalonado detrás de él, con el sobre DSSE mantenido ágil respecto al algoritmo y el algoritmo registrado por cápsula.

La traza de auditoría por ejecución

Cada ejecución es una secuencia de eventos solo-anexar y encadenada por hash (llamadas a LLM, llamadas a herramientas, recuperaciones, generación de subagentes, aprobaciones humanas, decisiones de barrera de seguridad), con una raíz de Merkle sobre todos ellos y una firma DSSE sobre la raíz. Solo se almacenan hashes de entradas y salidas, nunca las cargas útiles, así que la traza preserva la privacidad por defecto. El verificador no se limita a devolver válido o inválido; nombra la garantía que se rompió.

Operación (ejecución de 102 eventos) Coste Escalado
Registrar un evento ~121 us plano, bajo 0,01% de un paso
Sellar, construir la raíz 0,15 ms lineal en eventos
Verificar, recomputar todas las hojas 9,2 ms lineal, unas 60x el sellado

Registrar es menos de una centésima de un por ciento de un paso típico de agente, que está dominado por el ida y vuelta del modelo, así que la sobrecarga no es razón para muestrear y la traza se mantiene completa en lugar de estadística. La verificación es mucho más pesada que el sellado porque recomputa cada hoja a partir del contenido mientras que el sellado solo construye el árbol sobre hashes ya presentes. Esa asimetría es la correcta para una traza de auditoría, donde registrar es constante y verificar es raro, y marca la reverificación masiva como el sitio donde el núcleo nativo y el paralelismo rinden a continuación. Para hacer concretas las garantías, una demo ataca una ejecución sellada de ocho maneras, cada una rechazada con un motivo específico:

Ataque Rechazado como
Reescribir una salida del modelo discrepancia de hash de hoja
Borrar o insertar un evento discrepancia en el número de eventos
Reordenar eventos ruptura de la cadena
Truncar la ejecución discrepancia de cabeza de cadena
Degradar el algoritmo de hash discrepancia de hash de hoja
Presentar bajo la clave equivocada la firma no verifica

El registro de transparencia entre ejecuciones

Una cápsula válida por ejecución no puede probar que el conjunto de ejecuciones esté completo. Un operador que descarta una ejecución incómoda tiene una colección de cápsulas individualmente perfectas y una historia con un agujero. Eso lo cerramos con un único registro solo-anexar de raíces de cápsula que admite pruebas de inclusión (una ejecución está en el registro) y pruebas de consistencia (el registro en un tamaño anterior es un prefijo exacto del registro actual). La consistencia detecta la censura: un registro reconstruido con una ejecución eliminada está internamente bien formado y cada cápsula superviviente aún verifica, pero falla una comprobación de consistencia contra la raíz publicada antes, y la misma comprobación rechaza una inserción antedatada. Esto solo funciona contra una raíz comprometida antes de la manipulación, así que esa raíz debe vivir en algún sitio que el operador no pueda revisar, como un ancla pública.

Usamos un Merkle Mountain Range en lugar de un árbol de Merkle simple porque un anexado solo añade nodos y nunca reescribe uno, lo que permite que el registro viva en almacenamiento de escritura única o de objetos y mantiene válidas las pruebas de inclusión antiguas a medida que el registro crece. Esa propiedad es casi óptima, no solo cómoda: ePrint 2025/234 prueba que cualquier compromiso solo-anexar sucinto debe inducir un número superlineal de actualizaciones de testigo, y una variante cercana de MMR prácticamente alcanza la cota.

Tamaño del registro Construcción MMR Reconstrucción simple
500 1,3 ms 158 ms (120x)
1000 2,6 ms 618 ms (237x)
2000 5,2 ms 2491 ms (483x)

Anexar se mantiene plano a medida que el registro crece (unos 390k anexados por segundo tanto con 100 como con 10 000 entradas) mientras que reconstruir un árbol simple cae de 8176 a 84 raíces por segundo en el mismo rango, la firma cuadrática frente a linealítmica. El almacenamiento se estabiliza en 2,00 hashes guardados por entrada y las pruebas de inclusión crecen solo de 7 a 17 pasos entre 100 y 100 000 entradas. Dónde perdemos: nuestras pruebas de consistencia son O(log^2 n), llevando un camino por pico del registro antiguo en lugar del O(log n) alcanzable, unos pocos kilobytes con 100 000 entradas y un intercambio deliberado por una construcción que un revisor puede comprobar leyendo.

Límites que no cruzamos

El hashing da evidencia de manipulación, no inmunidad a ella. Un adversario que reconstruye una ejecución y recomputa su raíz produce un registro autoconsistente, porque una raíz de Merkle prueba que los eventos coinciden con la raíz, no que la raíz sea la que se publicó. La firma detecta eso, y el ancla pública prueba el momento. El registro de transparencia descansa en el mismo supuesto a lo grande: detecta una ejecución borrada solo respecto a una raíz comprometida antes del borrado. Declaramos estos límites en el producto y en sus demos, y una prueba afirma que la demo de falsificación sigue mostrando el ataque que no detenemos, para que el caso honesto no pueda desaparecer en silencio.

Reproducibilidad

Cada cifra aquí proviene de un benchmark incluido que se reejecuta con un solo comando, junto a las pruebas, las suites de conformidad entre lenguajes y entre implementaciones, y la matriz de CI que ejercita Python 3.10 a 3.14 con y sin el núcleo nativo. La metodología, el JSON en bruto y las notas de decisión (incluidas las dimensiones donde cada elección pierde) están en el repositorio, porque una capa que hace la IA auditable tiene que ser auditable ella misma.