← Todos los textos

Una firma dice quién, no qué

Una transacción firmada parece definitiva. Pero una firma prueba quién aprobó algo válido, no que lo que se ejecuta sea lo que aprobó. Esto es lo que DeFiLoops pone en ese hueco: un recibo de contenido y una simulación que juzga lo que una transacción hace de verdad.

Seguridad5 min de lecturaDeFiLoops

Una transacción firmada parece el final de la historia. Alguien con autoridad la aprobó, la firma verifica, la red la honrará. Listo.

Pero entre aprobado y ejecutado hay un hueco, y es más ancho de lo que parece. Entre el momento en que un agente firma y el momento en que un bloque incluye la transacción se elige una ruta, se construye el calldata y pasan minutos. Una firma es una afirmación sobre quién aprobó; no dice casi nada sobre qué acaba ejecutándose. Cerrar ese hueco es casi todo el trabajo de seguridad, y nada de ello es la firma.

Qué firma realmente el agente

Nuestro agente nunca firma una transacción en bruto. Firma una intención tipada —un importe, un mercado nombrado de una lista, un rango— y el adaptador construye la llamada a partir de esos parámetros. Esa es toda la razón por la que un agente comprometido no puede apuntar una llamada a un atacante: nunca se le dio un destino al que apuntar (lo escribimos aquí).

Pero tipada o no, una firma solo cubre los campos que hashea. Y un mensaje EIP-712 hashea un subconjunto.

Esa es la grieta. Cualquier cosa capaz de cambiar uno de esos campos no cubiertos entre la firma y la ejecución cambia lo que ocurre dejando todas las firmas verificando. Volver a comprobar que la intención sigue siendo válida no ayuda: la intención cambiada también es válida. Ambas comprobaciones coinciden, sobre intenciones distintas.

Dos aprobaciones, con minutos de diferencia

Un plan de adaptadores se autoriza dos veces, a propósito, porque en la primera aprobación el calldata todavía no existe.

  1. Firmar el grupo y acuñar un recibo

    El firmante valida cada paso, firma el mensaje EIP-712 de cada uno y acuña un recibo de validación: una afirmación, firmada con la misma clave, que dice validé intenciones con exactamente estos hashes de contenido, bajo exactamente estos límites, para esta cuenta en esta red. El hash de contenido cubre todos los campos, no el subconjunto firmado.

  2. Construir la ruta que el plan aún no podía conocer

    Solo ahora el enrutado elige los pools del intercambio y ensambla el calldata real. Esta es toda la razón de que haya una segunda aprobación: los bytes no existían hace un momento.

  3. Autorizar los bytes exactos y comprobar el recibo

    El segundo salto recalcula el hash de contenido de lo que tiene entre manos y lo contrasta con el recibo. La misma intención, bit a bit, incluyendo todos los campos que la cadena nunca verá. Si un solo campo no cubierto cambió, los hashes discrepan y rechaza.

El recibo hace una cosa precisa: sustituye «¿esto es válido?» —que el primer salto ya respondió y el segundo no puede volver a responder de forma útil— por «¿esto es lo mismo?», que es la pregunta que importa de verdad. Todo lo que el segundo salto solo puede saber en el segundo salto, como si la ruta se llenó bien, lo sigue sabiendo allí. La frescura nunca se confía al recibo: uno acuñado a las 12:00 no dice nada sobre las 12:20, así que el momento se vuelve a comprobar igualmente.

La comprobación que mira lo que una transacción hace

Todo lo anterior prueba que la transacción es la que se aprobó. No prueba que sea segura: una intención correctamente firmada y correctamente emparejada aún puede enrutarse por un pool que se llena pésimamente, o dejar atrás una autorización. Así que antes de emitir nada, la ejecutamos.

El simulador ejecuta la transacción contra un bloque real y reciente sin emitirladebug_traceCall— e informa de cada llamada, log y cambio de estado. Luego una sola regla juzga el resultado. Es la única regla del sistema que mira lo que una transacción hace en vez de lo que se pidió.

Lo que la simulación rechaza antes de emitir
Una pérdida neta que el plan no declaró
La cuenta solo puede perder lo que el plan dice que pierde. Cualquier cosa más allá, rechazada.
Cualquier autorización sobrante
Toda autorización (propietario, gastador) debe acabar en cero. Una autorización en pie es lo más valioso que un atacante puede dejar atrás: sobrevive a la transacción y nada más la busca.
Una salida por debajo del suelo
Lo que el plan compra debe llegar al suelo que el plan ya firmó, o por encima. Una ruta mal llenada se rechaza aquí —barato, antes del gas— en vez de revertir en la cadena.
Un NFT que ningún paso movió
Ninguna posición ni token se mueve salvo que un paso del plan lo mueva. Una posición de Uniswap es un NFT, y su id es un número que ya se ha confundido antes con un importe.

El truco que lo hace posible es que la simulación devuelve los números reales, así que no necesita oráculo de precios: compara contra el suelo que el plan ya lleva. Y una llamada que revierte no es un error que esquivar; es el hallazgo. Si cualquier llamada del paquete revierte, ningún efecto de la transacción persiste, y rechazar antes de emitir convierte una reversión en cadena con gas desperdiciado en un no barato y legible.

Todo lo que hay en el hueco falla cerrado

Ponlos en fila y la forma es la misma en cada paso: la respuesta segura es no, y es la de por defecto.

quién
lo que prueba una firma
qué
lo que prueban el recibo y la simulación
no
la respuesta cuando algo discrepa

Una guarda sin límites rechaza. Un recibo ausente rechaza. Un hash de contenido que no coincide rechaza. Una simulación que no coincide con el plan rechaza, antes de emitir un solo byte. Ninguna de esas cosas es la firma. La firma nunca fue lo difícil; solo probaba que alguien aprobó algo. La cadena de comprobaciones existe para probar que es esto, y que esto hace lo que el plan decía.

Cómo evaluar cualquiera de esto

No hace falta que confíes en esta página. La pregunta que separa una cadena de ejecución real de una firma y una plegaria es corta: entre el momento en que se aprueba y el momento en que aterriza, ¿qué se comprueba?

Si la respuesta es «la firma verifica», entonces todo campo que una firma no cubre queda abierto, y se confía por completo en lo que sea que construya el calldata. Si la respuesta nombra un recibo de contenido y una simulación de efectos, entonces las dos cosas que una firma no puede hacer —probar identidad y juzgar lo que ocurre de verdad— las está haciendo algo que sí puede.