Modelos UTXO y de cuentas · Lección 1 de 6
El modelo UTXO para analistas
10 min de lectura
Bitcoin y las cadenas derivadas de este (Litecoin, Bitcoin Cash, Dogecoin y otras) no almacenan saldos. Almacenan salidas discretas que una transacción crea y otra destruye. Todo saldo, flujo, rastreo y clúster que un analista produce para estas cadenas se basa en esa estructura, y la mayoría de los errores de principiantes (contar dos veces, saldos fantasma, «pagos» que en realidad eran cambio) se deben a olvidarla.
Las monedas son salidas
Una salida de transacción es una cantidad más un script de bloqueo (el scriptPubKey). Se identifica por su outpoint: el txid de la transacción que la creó y su posición en la lista de salidas de esa transacción, escrito como txid:vout. Una salida solo tiene dos estados: no gastada o gastada, y se gasta exactamente una vez y en su totalidad. No existe el gasto parcial ni una «cuenta» de la que retirar fondos.
Una entrada no contiene una cantidad. Apunta a un outpoint anterior y proporciona los datos (firma, clave pública, script) que satisfacen el script de bloqueo de esa salida. Para saber cuánto vale una entrada, se busca la salida que gasta (consulta la lección Transacciones de Bitcoin sin procesar).
De esto se derivan tres consecuencias para el análisis:
| Lo que ven los usuarios | Lo que registra la cadena |
|---|---|
| «Mi dirección tiene 1,2 BTC» | Varias salidas no gastadas cuyos scripts se decodifican como esa dirección, que suman 1,2 BTC |
| «Envié 0,5 BTC» | Una transacción que consumió salidas completas y creó otras nuevas, normalmente con cambio incluido |
| «Mi saldo disminuyó» | Salidas que pasaron del conjunto de no gastadas al conjunto de gastadas |
Por lo tanto, el saldo de una dirección es una cifra derivada, nunca una cifra almacenada. Las monedas nuevas entran mediante la transacción coinbase, que no tiene entradas reales y paga el subsidio del bloque más las comisiones. Las salidas coinbase solo se pueden gastar en un bloque que esté al menos 100 bloques después del que las creó; esta regla se denomina madurez coinbase.
El grafo de gasto
Como cada entrada apunta a exactamente una salida anterior, la cadena forma un grafo de gasto. Este es un pequeño ejemplo decodificado:
{
"txid": "b7e0…5d19",
"vin": [
{ "txid": "4f3a…c21e", "vout": 0 },
{ "txid": "9b1d…07aa", "vout": 1 }
],
"vout": [
{ "n": 0, "value": 1.00000000, "address": "3J9x…Qe1m" },
{ "n": 1, "value": 0.09950000, "address": "bc1q…r8tn" }
]
}
La transacción b7e0…5d19 gasta la salida 0 de 4f3a…c21e (0,80 BTC) y la salida 1 de 9b1d…07aa (0,30 BTC). Las entradas suman 1,10 BTC; las salidas, 1,0995 BTC; la comisión es la diferencia: 0,0005 BTC. Las dos salidas ahora están no gastadas y solo se convertirán en aristas cuando algo las gaste.
Si se leen como un grafo, las transacciones son nodos y las salidas son aristas dirigidas desde la transacción que las creó hasta la transacción que las gastó. Las salidas no gastadas son aristas que aún no tienen destino. El grafo es acíclico y siempre apunta hacia adelante en el tiempo, de modo que cualquier satoshi se puede seguir hacia atrás, paso a paso, hasta las transacciones coinbase que lo crearon.
Hay una propiedad más importante que cualquier otra: dentro de una transacción, el protocolo no vincula entradas específicas con salidas específicas. Cuando entran 0,80 y 0,30 BTC y salen 1,00 y 0,0995 BTC, nada en la cadena indica qué entrada «pagó» qué salida. El valor se agrupa y se redistribuye. Todo método de rastreo que asigna entradas a salidas (primero en entrar, primero en salir, método de recorte y otros) es una convención del analista, no un hecho que se pueda leer en los datos; la lección Rastreo de fondos hacia adelante y hacia atrás los compara.
Salidas de cambio
Como las salidas se gastan enteras, un pago suele enviar una parte del valor al destinatario y devolver el resto al remitente como cambio. En el ejemplo anterior, probablemente una de las dos salidas sea cambio, pero el protocolo no indica cuál. Identificarla depende de heurísticas (tipo de script, importes redondos, antigüedad de la dirección), que se abordan en la lección Heurísticas para detectar el cambio.
Equivocarse al identificar el cambio tiene efectos importantes. Si se trata el cambio como un pago, se inventa una contraparte y el rastreo sigue la rama equivocada. Contar todas las salidas como valor transferido infla el «volumen» en cadena, porque el cambio y las transferencias propias no mueven valor entre partes. Las mediciones de volumen rigurosas intentan excluir el cambio y las consolidaciones, y la calidad de cada medición depende de su modelo de cambio.
Reconocer algunas formas comunes de transacciones ayuda:
| Forma | Estructura típica | Significado habitual |
|---|---|---|
| Pago simple | 1–3 entradas, 2 salidas | Pago más cambio |
| Consolidación | Muchas entradas, 1 salida | Una cartera que combina salidas pequeñas, a menudo cuando las comisiones son bajas |
| Pago por lotes | Pocas entradas, muchas salidas | Un servicio que paga a muchos clientes a la vez |
| Paso de pelado | 1 entrada, 2 salidas: una pequeña y otra grande | Parte de una cadena de pelado; la salida grande continúa (consulta la lección Métricas y patrones de grafos) |
Antigüedad y latencia de las monedas
Cada salida registra el bloque en el que se creó, por lo que toda salida no gastada tiene una antigüedad. Cuando se gasta una salida, los analistas calculan los días-moneda destruidos (CDD): la cantidad multiplicada por el número de días que permaneció sin gastarse. Una salida de 2 BTC creada 400 días antes destruye 800 días-moneda cuando se gasta. El CDD total de un día dividido por los BTC movidos ese día da una latencia promedio: cuánto tiempo, en promedio, llevaban inmóviles las monedas que se movieron.
La antigüedad importa en las investigaciones porque las monedas que han permanecido inactivas durante mucho tiempo y de pronto se mueven son una alerta clásica. Gran parte de los bitcoins robados de Bitfinex en 2016 permanecieron en la cartera del hacker durante años, hasta que las autoridades incautaron unos 94.000 BTC en 2022. Supervisar las salidas vinculadas a un incidente antiguo para detectar su primer gasto es una de las reglas más sencillas y valiosas que puede establecer un analista.
Un desglose del conjunto UTXO por antigüedad, a una altura fija, puede hacerse en SQL de la siguiente manera. El esquema es típico: una tabla outputs con una fila por salida y una tabla inputs en la que cada fila indica el outpoint que se gasta.
-- Salidas no gastadas a la altura 900.000, agrupadas por antigüedad (unos 144 bloques por día)
WITH spent AS (
SELECT prev_txid, prev_vout
FROM inputs
WHERE block_height <= 900000
)
SELECT
CASE WHEN 900000 - o.block_height < 144 * 30 THEN '1: menos de 1 mes'
WHEN 900000 - o.block_height < 144 * 365 THEN '2: de 1 a 12 meses'
ELSE '3: más de 1 año' END AS age_band,
COUNT(*) AS utxos,
SUM(o.value_sat) / 100000000.0 AS btc
FROM outputs o
LEFT JOIN spent s
ON s.prev_txid = o.txid AND s.prev_vout = o.vout
WHERE o.block_height <= 900000
AND s.prev_txid IS NULL -- sin gasto a esa altura o antes
GROUP BY 1
ORDER BY 1;
La consulta conserva solo las salidas creadas a esa altura o antes que no tienen un gasto coincidente a esa altura o antes; esa es la definición del conjunto UTXO en ese punto. Los intervalos de antigüedad usan el recuento de bloques como aproximación del tiempo; para obtener edades precisas, hay que unirlos con la tabla de bloques y usar las marcas de tiempo de los bloques (teniendo en cuenta las salvedades sobre las marcas de tiempo que se explican en la lección Los bloques y encabezados como datos). Nótese el divisor 100000000.0: en algunos motores, la división entera truncaría el resultado de forma silenciosa.
El conjunto UTXO
El conjunto de todas las salidas no gastadas en un bloque determinado es el conjunto UTXO. Los nodos lo conservan en una base de datos específica (chainstate), porque es lo que necesitan para validar nuevas transacciones. Ha crecido mucho, en parte debido a experimentos con inscripciones y tokens que crearon muchas salidas pequeñas, por lo que las consultas sobre este conjunto son pesadas.
Dos métodos RPC de Bitcoin Core resultan útiles en este caso:
gettxoutsetinfodevuelve estadísticas resumidas del conjunto, incluidostxouts(el número de salidas no gastadas) ytotal_amount(las monedas que contienen). Consultar una altura histórica requiere el índice opcional coinstatsindex.scantxoutsetbusca en el conjunto UTXO actual salidas que coincidan con descriptores de salida comoaddr(...)y devuelve cada coincidencia con su txid, vout, importe y altura. Solo detecta salidas no gastadas, nunca el historial.
Bitcoin Core no mantiene un índice de direcciones, por lo que para obtener el historial completo de transacciones de una dirección se necesita un indexador o un almacén de datos (consulta la lección API de nodos y acceso a datos).
El conjunto también sirve como comprobación de coherencia. Su total_amount debería estar cerca del subsidio total emitido hasta la fecha, pero por debajo de este, porque hay valor que se puede demostrar que no se puede gastar y que nunca entra en el conjunto: la recompensa del bloque génesis, las salidas OP_RETURN y las comisiones o el subsidio que los mineros no reclamaron. Si el total UTXO calculado por una canalización supera la emisión, hay un error, normalmente gastos omitidos o salidas duplicadas.
Reutilización de direcciones
Las direcciones no son cuentas. Las carteras bien diseñadas derivan una dirección nueva para cada recepción a partir de una semilla determinista jerárquica, y el usuario nunca ve la mayoría de ellas. Una persona puede controlar miles de direcciones; una dirección de cartera caliente de un exchange puede interactuar con millones de contrapartes.
La reutilización de direcciones (recibir varias veces en la misma dirección) sigue siendo común: direcciones para donaciones, algunas direcciones de depósito de exchanges, carteras antiguas y usuarios descuidados. Para un analista, es tanto una ventaja como una trampa. La reutilización vincula pagos sin necesidad de heurísticas y, una vez que se gasta desde una dirección, su clave pública queda visible en la cadena. Pero las métricas por dirección, como «direcciones activas» o «direcciones con más de 1 BTC», miden scripts, no personas, y nunca deben presentarse como recuentos de usuarios.
Un detalle sutil: una misma clave pública puede generar varias direcciones diferentes, una por tipo de script (por ejemplo, P2PKH heredado y P2WPKH SegWit nativo). Parecen no estar relacionadas, pero cuando la clave se revela en un gasto, un analista puede vincularlas mediante la propia clave pública.
Enfoque del examen: una salida se identifica por su outpoint (txid:vout) y se gasta una sola vez y en su totalidad; las entradas no contienen importes, por lo que los valores de las entradas y los saldos se derivan de las propias salidas; el protocolo no vincula entradas específicas con salidas específicas, por lo que el rastreo de entradas a salidas es una convención; las salidas de cambio y por qué clasificarlas mal distorsiona los rastreos y el volumen; la madurez coinbase de 100 bloques, los días-moneda destruidos (cantidad × días sin gastar) y la latencia como alertas; cómo calcular el conjunto UTXO a una altura determinada (creadas a esa altura o antes, no gastadas a esa altura o antes);gettxoutsetinfoyscantxoutset, y sus limitaciones; la reutilización de direcciones y por qué los recuentos de direcciones no equivalen a recuentos de usuarios.