Un bloque de Bitcoin, byte a byte
Pasamos de DCoin a un bloque histórico de Bitcoin: separamos el registro de disco de la cabecera de 80 bytes, decodificamos nBits, recalculamos el hash y analizamos una transacción coinbase de formato clásico completa. Los esquemas de campos, los datos hexadecimales exactos y un pequeño decodificador conectan los conceptos con su representación serializada.
Un explorador de bloques convierte un bloque de Bitcoin en etiquetas ordenadas: fecha, número de transacciones, hash, recompensa. Son interpretaciones de una secuencia de bytes. Leerla por nuestra cuenta permite distinguir los datos reales de las convenciones de presentación y de los detalles del programa que guarda el bloque.
Dejamos el modelo simplificado de DCoin para estudiar un ejemplo histórico de Bitcoin. La cabecera es de julio de 2011; el archivo se examinó en una instalación posterior de Bitcoin Core. El ejemplo clásico resulta más fácil de leer, pero no describe todos los formatos actuales de transacción o almacenamiento.
Disponemos del prefijo del registro de archivo, la cabecera completa, el número de transacciones y la primera transacción completa. Aquí no se reproducen las 154 transacciones. Podemos verificar el hash de la cabecera y analizar la coinbase, pero no reconstruir todo el árbol de Merkle solo con este extracto.
4.1 Separar el registro de archivo del bloque
El registro examinado de blk00003.dat empieza así:
f9 be b4 d9 c2 bf 00 00 01 00 00 00 ...Los primeros cuatro bytes identifican el límite del registro de la red principal. Los cuatro siguientes codifican la longitud del bloque serializado como entero sin signo en little-endian: c2 bf 00 00 es 0x0000bfc2, es decir, 49.090 bytes. El bloque empieza después de esos ocho bytes de delimitación.
Los campos de identificación y tamaño no pertenecen a la cabecera de 80 bytes ni entran en el hash de la prueba de trabajo. Si obtenemos el bloque serializado mediante bitcoin-cli getblock BLOCK_HASH 0, el resultado empieza en la cabecera, sin ese prefijo de archivo.
Hay además una particularidad del almacenamiento moderno. Bitcoin Core 28.0 introdujo por defecto la ofuscación XOR de los archivos de bloques, con una clave guardada en el directorio de bloques. Por eso un volcado hexadecimal de una instalación reciente puede no parecerse a este registro hasta deshacer esa codificación. Es un detalle del almacenamiento local, no un cambio en la serialización de consenso del bloque. Véanse las notas de la versión 28.0.

Sesión original de terminal que localiza los archivos de bloques en el directorio de datos de Bitcoin Core. Ampliar imagen.
Bytes de delimitación del registro resaltados (2 extractos)

Extracto original que resalta los cuatro bytes de identificación. Ampliar imagen.

Extracto original que resalta la longitud de 49.090 bytes. Ampliar imagen.
4.2 Leer la cabecera de 80 bytes
Respecto al inicio del bloque serializado, los campos de la cabecera son:
| Desplazamiento | Bytes | Campo | Ejemplo |
|---|---|---|---|
| 0 | 4 | Versión | 01 00 00 00 |
| 4 | 32 | Hash de la cabecera anterior | Empieza por 47 0c 5f a9 |
| 36 | 32 | Raíz de Merkle | Empieza por 10 f5 06 59 |
| 68 | 4 | Marca de tiempo | de eb 23 4e |
| 72 | 4 | Umbral compacto, nBits |
cf bb 0a 1a |
| 76 | 4 | Nonce | e2 5b 6f 9a |
La marca de tiempo se decodifica como 1310976990, o 2011-07-18 08:16:30 UTC. Es un valor declarado en la cabecera y limitado por las reglas de consenso, no una medida precisa de cuándo recibió el bloque cada nodo. El nonce es 2590989282. La versión 1 corresponde a esta cabecera histórica; no significa que todos los bloques actuales usen la versión 1.
La cabecera completa es lo bastante corta como para reproducirla y calcular su hash:
from datetime import datetime, timezone
from hashlib import sha256
from struct import unpack
def hash256(data):
return sha256(sha256(data).digest()).digest()
header = bytes.fromhex("""
01000000
470c5fa9f8d729d0ecc704d0b7d12331c5f415bd47e490a70003000000000000
10f5065927d8ec42cfd80454e8a77685fb44d6a8a0d05aaa62381b0a37786c33
deeb234e cfbb0a1a e25b6f9a
""")
assert len(header) == 80
version, previous, merkle, timestamp, bits, nonce = unpack("<I32s32sIII", header)
print(datetime.fromtimestamp(timestamp, timezone.utc).isoformat())
print("anterior:", previous[::-1].hex())
print("merkle: ", merkle[::-1].hex())
print("bloque: ", hash256(header)[::-1].hex())El hash del bloque, en su representación habitual, es
00000000000007b9c57dce62d80b9e4d4d2c11af90b000feb5450daa53328378La inversión [::-1] es una convención de presentación de estos identificadores. Calcular el hash del texto hexadecimal impreso, aplicar el hash una vez en lugar de dos o incluir los ocho bytes de delimitación del archivo da un resultado distinto.

Extracto original de la cabecera. Los campos resaltados corresponden a la estructura de 80 bytes que acabamos de decodificar. Ampliar imagen.
4.3 Convertir nBits en un umbral
Los bytes cf bb 0a 1a representan el entero 0x1a0abbcf. En esta codificación compacta, el byte superior es un exponente y los bits inferiores contienen un coeficiente y un indicador de signo.
En este ejemplo positivo, el exponente es y el coeficiente . Como , el umbral es
Se trata de un límite, no de un hash exacto que el minero deba encontrar. El hash de la cabecera, interpretado en el orden numérico de Bitcoin, debe ser menor o igual que .
Ampliamos el fragmento anterior de Python:
exponent = bits >> 24
coefficient = bits & 0x007fffff
assert not bits & 0x00800000 # Este ejemplo no es negativo.
target = (coefficient << (8 * (exponent - 3)) if exponent >= 3
else coefficient >> (8 * (3 - exponent)))
assert int.from_bytes(hash256(header), "little") <= target
assert hash256(header)[::-1].hex() == (
"00000000000007b9c57dce62d80b9e4d4d2c11af90b000feb5450daa53328378"
)
print(hex(target))Un validador completo comprueba también que el umbral sea positivo, esté dentro del límite de prueba de trabajo de la red y sea exactamente el exigido en esa posición de la cadena. No bastaría con superar una comparación frente a un umbral fácil elegido por el propio minero. La referencia de cabeceras documenta los casos límite de la codificación compacta.
4.4 Localizar las transacciones
En el desplazamiento 88 del archivo, justo después de la cabecera, el byte 9a codifica un total de 154 transacciones. Es un entero CompactSize. Los valores pequeños ocupan un byte; los mayores empiezan con un marcador seguido de un entero en little-endian.
| Primer byte | Bytes siguientes | Rango decodificado |
|---|---|---|
00–fc |
0 | 0–252 |
fd |
2 | 253–65.535 |
fe |
4 | 65.536–4.294.967.295 |
ff |
8 | 4.294.967.296–18.446.744.073.709.551.615 |
Esta convención también se usa en el número de entradas y salidas y en las longitudes de los scripts. Hay que distinguir estos enteros de longitud variable de los campos fijos de cuatro bytes. Leer todos los recuentos como números de 32 bits desplazaría el cursor y corrompería la interpretación de lo que sigue.
En la primera transacción, 01 significa una entrada. Más adelante, 08 indica un script de entrada de ocho bytes y 43, un script de salida de 67 bytes. Aunque aparezcan en estructuras diferentes, cumplen el mismo papel de CompactSize.

Extracto original del recuento: 9a representa 154 transacciones. Ampliar imagen.
4.5 Decodificar la transacción coinbase
La primera transacción empieza en el desplazamiento 89 y ocupa 135 bytes. Su única entrada no gasta una salida anterior. Un hash de 32 bytes a cero seguido del índice ffffffff constituye el outpoint especial de la coinbase. Así reclama el bloque la recompensa permitida.
Los bytes del importe de salida son 60 a1 5f 2a 01 00 00 00. Como entero en little-endian representan 5.005.877.600 satoshis, es decir, 50,05877600 BTC. El subsidio de ese periodo era 50 BTC; la diferencia es 0,05877600 BTC. Si el minero reclamó toda la recompensa disponible, esa diferencia son las comisiones incluidas. La cantidad de la coinbase, por sí sola, no descarta que haya comisiones adicionales sin reclamar.
El script de salida empieza por 41, que ordena introducir los siguientes 65 bytes, continúa con una clave pública sin comprimir que empieza por 04 y termina en ac, OP_CHECKSIG. Es una salida de pago a clave pública. El script establece una condición de gasto futura; no es una firma que autorice una entrada ordinaria en la propia coinbase.
| Campo | Descripción | Tamaño |
|---|---|---|
| Versión | Versión de la transacción; 1 en este ejemplo. | 4 bytes |
| Número de entradas | Cantidad de entradas, codificada como entero CompactSize canónico. | 1–9 bytes |
| Entradas | Outpoint anterior, script de entrada y secuencia de cada entrada. Una coinbase tiene una única entrada especial con outpoint nulo. | Variable; tantas entradas como indique el recuento |
| Número de salidas | Cantidad de salidas, codificada como entero CompactSize canónico. | 1–9 bytes |
| Salidas | Importe en satoshis y script de bloqueo de cada salida. Las salidas de la coinbase reclaman el subsidio y las comisiones que se decida cobrar. | Variable; tantas salidas como indique el recuento |
| Locktime | Cero desactiva el bloqueo temporal absoluto. Otro valor indica una altura de bloque o una marca de tiempo; se ignora si todas las secuencias de entrada son 0xffffffff. | 4 bytes |

Extracto original de la entrada coinbase: el hash a cero y el índice ffffffff identifican el outpoint especial. Ampliar imagen.

Extracto original de la salida, incluido el importe de 5.005.877.600 satoshis. Ampliar imagen.
Este es un decodificador completo de la transacción. Comprueba truncamientos, codificaciones canónicas de longitud y la posición final del cursor, de forma que un límite de campo incorrecto no produzca silenciosamente un resultado que parezca razonable.
from io import BytesIO
from decimal import Decimal
raw = bytes.fromhex("""
01000000 01
0000000000000000000000000000000000000000000000000000000000000000
ffffffff 08 04cfbb0a1a02c002 ffffffff
01 60a15f2a01000000 43
4104c3bb8e003d617f17b9ea0f2d0d626af663342211bd854f61c9818b87ecd6c61c2e4cda448d91147c9d24d10dc7a72e7e7ab0beba4d44d3fa9509807cb99aa32cac
00000000
""")
stream = BytesIO(raw)
def take(size):
result = stream.read(size)
if len(result) != size:
raise ValueError("Transacción truncada")
return result
def uint(size):
return int.from_bytes(take(size), "little")
def compact_size():
first = uint(1)
if first < 0xfd:
return first
size, minimum = {0xfd: (2, 253), 0xfe: (4, 65536),
0xff: (8, 4294967296)}[first]
value = uint(size)
if value < minimum:
raise ValueError("CompactSize no canónico")
return value
assert len(raw) == 135
assert uint(4) == 1 # Versión
assert compact_size() == 1 # Número de entradas
assert take(32) == bytes(32) # Transacción anterior nula
assert uint(4) == 0xffffffff # Índice del outpoint coinbase
assert take(compact_size()) == bytes.fromhex("04cfbb0a1a02c002")
assert uint(4) == 0xffffffff # Secuencia
assert compact_size() == 1 # Número de salidas
value = uint(8)
script = take(compact_size())
assert value == 5_005_877_600
assert len(script) == 67 and script[:2] == b"\x41\x04"
assert script[-1] == 0xac # OP_CHECKSIG
assert uint(4) == 0 # Bloqueo temporal
assert stream.tell() == len(raw)
print(f"{Decimal(value) / Decimal(100_000_000):.8f} BTC")Es un lector comprobado para una transacción clásica concreta, no un analizador general de transacciones. Uno que procese datos no fiables también necesitaría límites de recursos y soporte para las demás codificaciones y estructuras permitidas. Las aserciones documentan el ejemplo; en producción conviene usar gestión explícita de errores, ya que Python puede desactivar las aserciones.
4.6 Mantener visible la frontera histórica
Tres detalles evitan convertir el ejemplo en una plantilla engañosa.
Primero, los ocho bytes del script de entrada coinbase son datos históricos de minería. Son anteriores a la regla que exige una altura de bloque al comienzo del script. BIP 34 introdujo esa obligación. No debemos interpretar sus primeros bytes como una altura moderna ni descartar todos los scripts coinbase como relleno sin significado.
Segundo, Segregated Witness añade reglas de serialización y compromisos que esta transacción clásica no utiliza. BIP 141 describe el formato de testigo, el peso del bloque y el compromiso de testigo en la coinbase cuando es obligatorio. Un lector moderno debe reconocer el marcador y el indicador correspondientes, en lugar de interpretar ciegamente cada byte posterior a la versión como un recuento de entradas clásico.
Tercero, analizar no equivale a validar por completo. No hemos comprobado todas las transacciones, recalculado su raíz de Merkle, reconstruido el conjunto UTXO ni establecido el trabajo acumulado de la cadena circundante. Lo que sí hemos hecho es más acotado y reproducible: separar la delimitación de los bytes de consenso, decodificar una cabecera real, contrastar su hash con el umbral codificado y cuadrar una transacción coinbase completa.
En la última parte encontraremos estas mismas estructuras dentro de mensajes de red, con otra capa de delimitación alrededor y donde el límite de un paquete no tiene por qué ser el límite de un mensaje.