El 6 de septiembre de 2026, la red Liquid Network sufrió un ataque que aprovechó una vulnerabilidad en la base de código de Elements, que se utiliza para verificar transacciones confidenciales.
El problema surgió por la formación ambigua de la clave de caché al verificar el rangeproof, la prueba criptográfica que confirma que el importe oculto de una transacción es admisible. Por este motivo, dos conjuntos distintos de datos de verificación podían dar lugar a una misma entrada en la caché.
Primero el atacante preparó transacciones especiales y llenó la caché con ellas, y después envió una transacción maliciosa que aumentaba el volumen de activos emitidos: reutilizó un resultado de verificación ya almacenado y permitió crear L-BTC no autorizados.
Después de eso, el atacante cambió esos activos dentro de la red Liquid por bitcoins reales mediante el mecanismo de retirada de fondos a la red principal de Bitcoin.
Antecedentes
Los bitcoins bloqueados en una cartera de Bitcoin específica, controlada por la federación Liquid, respaldan los L-BTC emitidos en Liquid. Según el sistema de anclaje previsto, un L-BTC debe corresponder a un BTC que se encuentre bajo el control de la federación.
En lugar de publicar el importe, la transacción puede contener un compromiso criptográfico de Pedersen. El propio activo también puede representarse mediante un compromiso similar. Una ecuación matemática específica confirma que los valores de entrada y de salida de la transacción están equilibrados, mientras que el llamado rangeproof demuestra que cada importe oculto se encuentra dentro del rango no negativo permitido.
En particular, la verificación del rangeproof forma parte del mecanismo que debe garantizar que el importe oculto sea realmente correcto y no pueda utilizarse para crear activos por encima de la cantidad permitida. Al mismo tiempo, la comprobación real de la corrección depende de varios elementos a la vez: el rangeproof, el compromiso por el valor, el compromiso por el activo y el scriptPubKey.
El script se transmite a secp256k1_rangeproof_verify como datos adicionales incluidos en la comprobación criptográfica. Una prueba válida para una combinación (C, X, S) no pasa a ser válida automáticamente para otra combinación.
Transacciones clave
| Fecha/hora | TXID | Descripción |
|---|---|---|
| 2026-09-06 13:52:10 | 271147100a94f6337b6c3db39b30c92d5b97ed91597307b6f721f73a15187ec5 | Transacción preparatoria |
| 2026-09-06 13:53:10 | f24a4b179b5cc7e88b25a763911f7cbdf2bf45d1d1b5ab611e94461cef0a183f | La transacción que creó un volumen adicional de L-BTC dentro de Liquid |
| 2026-09-06 13:54:10 | 3875a6d6ed4af708e6fd90d1c5504dc014e52c7093a566987252006d6cf1146b | La transacción crea nuevas salidas confidenciales de Liquid controladas por el atacante o por la siguiente cartera |
| 2026-09-06 14:00:10 | c6ea588ac26f5838b6acbb2a444a33b325bbfeb39bf16dfe27b47215ffd72267 | La transacción crea nuevas salidas confidenciales de Liquid controladas por el atacante o por la siguiente cartera |
| 2026-09-06 14:01:10 | 46f117c990580501a5156937a8c8affda551a38b3c0b99d29eb8469ee6beb3d2 | Retirada de Liquid por un importe de 2.65138358 L-BTC |
| 2026-09-06 14:06:10 | ce4caece413cd9d444ce7ed9f54e5b328b3da5e4af301aff59a3571f76e988f2 | Retirada de Liquid por un importe de 3,996.01834922 L-BTC |
| 2026-09-06 14:28:56 | 85d2ca15bea33a592e73ed40c6a5da887feecf1e77f58ec7f580e00841645043 | 3,996.01834922 BTC enviados a la dirección bc1ql4mfu6aundtkksxklfajs2h3t9nzcd6gyqjlte |
| 2026-09-07 18:09:25 | a6d697a25266ce3c78774fd1d75f896b7af522ada209b0f6228ea497bc49a46d | 3,400 BTC devueltos a la dirección bc1qdlld6antmv4xug242ed83q7k4rqw50cwfns38szx4qu2f4jwaxxsuhwxxr |
Direcciones clave
| Dirección | Uso |
|---|---|
| ex1q7kgx4ptje7px48tn0nsmc6se5pngdp3smpqa2w | Dirección del atacante en la red Liquid |
| bc1qgslsydz56d0ed6827hdemfmk5w2f6ldyc6wt7p | Receptor del pago de la federación por 3,996.01834922 BTC |
| bc1ql4mfu6aundtkksxklfajs2h3t9nzcd6gyqjlte | Receptor del pago de la federación por 2.65138358 BTC |
| bc1ql4mfu6aundtkksxklfajs2h3t9nzcd6gyqjlte | Dirección del atacante para consolidar fondos y transmitir mensajes en la blockchain |
| bc1qdlld6antmv4xug242ed83q7k4rqw50cwfns38szx4qu2f4jwaxxsuhwxxr | Cartera de Bitcoin de la federación Liquid utilizada para el anclaje y la retirada de fondos |
1. Falsificación de la caché de pruebas
Para el análisis se obtuvieron y descompusieron los datos brutos de serialización de Elements, y no solo los campos que muestra un explorador de blockchain. Las salidas :0 de las dos transacciones preparatorias coinciden por completo en activo, compromiso por el valor, nonce, script, surjection proof y rangeproof.
Utilizando desplazamientos que empiezan en cero y un límite superior que no se incluye en el rango, a partir de los 8,882 bytes de la transacción bruta se decodificó el output_0 de la transacción 271147100a94f6337b6c3db39b30c92d5b97ed91597307b6f721f73a15187ec5, lo que dio los siguientes datos:
a. C0 = 09d6c6150e95e576d288dcc890bf48518119c6f227ea1be6f9a932792dbee683f5
b. S0 = 6a|43|086f5d67160fc4b477954fb09ef321e5b589d7a07740a1a6df494ed2335b1d01d8|0a0a488 de4899d0ae757f6cf8368663184d164106111ed9eaecf510e35282ddc6d|6a.
i. 6a significa OP_RETURN. ii. 43 indica que a continuación deben colocarse 67 bytes.
Esos 67 bytes tienen deliberadamente la siguiente estructura: un C1 de 33 bytes, una X de 33 bytes y un 6a de un solo byte. Así, el script S0 = 6a43 || C1 || X || 6a, donde X = 0a0a488de4899d0ae757f6cf8368663184d164106111ed9eaecf510e35282ddc6d es el resultado de ejecutar secp256k1_generator_generate(L-BTC asset ID).
2. Llenado de la caché en el mempool activo
Al conectar un bloque se aplican reglas de caché según las cuales una entrada existente se consume, pero si no hay entrada, no se guarda una nueva. Por eso, la conexión del bloque 4,050,335 debería haber eliminado la entrada de caché creada en el momento en que se aceptaron por primera vez las dos transacciones preparatorias visibles.
En consecuencia, para que la transacción que creaba L-BTC adicionales pudiera pasar la verificación después de conectarse el bloque 4,050,335 y antes de comprobarse la salida incorrecta, en cada nodo que aceptaba la transacción se necesitaba una nueva transacción válida con la misma secuencia (P0, C0, X, S0).
El identificador de esta transacción de relleno y sus bytes originales aún no se conocen. Para encontrarlos podrían hacer falta el archivo debug.log de uno de los functionaries, el mempool.dat vigente en ese momento, un registro del intercambio de datos entre nodos o una copia guardada del estado del mempool.
3. Creación de L-BTC adicionales dentro de Liquid
La decodificación del output_1 de la transacción de inflación f24a4b179b5cc7e88b25a763911f7cbdf2bf45d1d1b5ab611e94461cef0a183f dio los siguientes datos:
a. C1 = 086f5d67160fc4b477954fb09ef321e5b589d7a07740a1a6df494ed2335b1d01d8.
Son exactamente los primeros 33 bytes de los datos transmitidos por el script OP_RETURN de la salida preparatoria.
Cabe señalar que el rangeproof P1 es 68 bytes más largo que P0, y esos datos adicionales se encuentran al final: 09d6c6150e95e576d288dcc890bf48518119c6f227ea1be6f9a932792dbee683f5
b. 0a0a488de4899d0ae757f6cf8368663184d164106111ed9eaecf510e35282ddc6d6a43, lo que da el C0 de la transacción preparatoria: 09d6c6150e95e576d288dcc890bf48518119c6f227ea1be6f9a932792dbee683f5
y X = 0a0a488de4899d0ae757f6cf8368663184d164106111ed9eaecf510e35282ddc6d es el resultado de ejecutar secp256k1_generator_generate(L-BTC asset ID). La incorporación del generador exacto de L-BTC permitió que los bytes del script de la transacción preparatoria se alinearan con el campo del generador del activo en la transacción atacante.
c. En conjunto
Transacción preparatoria válida: P0 || C0 || X || (6a43 || C1 || X || 6a)
Transacción atacante no válida: (P0 || C0 || X || 6a43) || C1 || X || 6a
Ambas se reducen finalmente al mismo flujo de 4,301 bytes: P0 || C0 || X || 6a43 || C1 || X || 6a
4. Retirada de los fondos
Los L-BTC pueden cambiarse de nuevo por BTC reales que se encuentran bajo el control de la federación Liquid. La transacción siguiente, 3875a6d6…f1146b, utilizó la salida f24…:0 de la transacción vinculada al ataque. Otras dos transacciones crearon salidas explícitas para retirar fondos a la red principal de Bitcoin:
- 46f117c9…eb3d2: 2.65138358 L-BTC
- ce4caece…988f2: 3,996.01834922 L-BTC
La transacción de Bitcoin 8db751a6…7b140, confirmada en el bloque 965,783, pagó los dos importes exactos retirados como resultado del ataque.
¿En qué consistía la esencia de la vulnerabilidad?
Elements almacena en caché los rangeproof verificados con éxito para no ejecutar dos veces la misma operación criptográfica costosa. Aquí importan dos construcciones.
Ese diseño no tenía en cuenta el generador del activo ni el script, aunque ambos parámetros influyen en la validez de la prueba. El commit c26d719 intentó vincular la entrada de caché con todo el contexto de verificación.
El parche con la implementación de la caché
Transacción preparatoria válida: (P0, C0, X, 6a43 || C1 || X || 6a)
Transacción atacante no válida: (P0 || C0 || X || 6a43, C1, X, 6a)
Las salidas observadas de la transacción preparatoria y de la atacante no generan una colisión si se utiliza la clave más antigua P || C. Sus datos de entrada para la clave antigua difieren tanto en longitud como en contenido.
La construcción del ataque, en cambio, se corresponde exactamente con la clave de cuatro campos sin separadores introducida por esa corrección. Por eso, el hecho de que la transacción pasara con éxito apunta a una implementación similar a c26d719 combinada con una caché llenada previamente. Al mismo tiempo, las versiones exactas de los binarios desplegados en los distintos nodos functionaries de Liquid no se han divulgado públicamente.
Problema de sincronización: cómo el protocolo Verus perdió $7.5M por las diferencias entre blockchainsEl movimiento de los fondos
Un autoproclamado «white hat» devolvió 3,400 BTC, cuyo valor era de $268M, y se quedó con 598.5 BTC por valor de $47M - el 15% del total - como recompensa por haber encontrado la vulnerabilidad.
Dirección: bc1ql4mfu6aundtkksxklfajs2h3t9nzcd6gyqjlte.
