Orden de Bytes
¿Por qué los hashes de transacciones y bloques aparecen invertidos?
Una peculiaridad divertida de Bitcoin es que los hashes de transacción y los hashes de bloque tienen el orden de los bytes invertido cuando los muestras y cuando los buscas.
Por ejemplo, el hash de bloque del bloque génesis sale de la función hash así:
6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000Pero, cuando buscas ese bloque en Bitcoin Core o en un explorador, ves este orden de bytes:
000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26fComo puedes ver, el orden de los bytes se ha invertido.
- Siempre que trabajas con hashes de transacción/bloque internamente (por ejemplo, dentro de datos brutos de Bitcoin), usas el orden de bytes natural.
- Siempre que estás mostrando o buscando hashes de transacción/bloque, usas el orden de bytes inverso.
Así que parte de trabajar con datos de Bitcoin es acostumbrarte a invertir el orden de los bytes para hashes de transacción y de bloque. Probablemente te confundirá algunas veces (como todavía me confunde a mí), pero, como dije, es solo una peculiaridad del desarrollo en Bitcoin.
¿Cuáles son los distintos órdenes de bytes?
Hay dos órdenes de bytes usados en Bitcoin:
- Orden natural (Natural Byte Order)
- Orden inverso (Reverse Byte Order)
Estos órdenes de bytes no deben confundirse con los órdenes little-endian y big-endian, que son los órdenes usados para representar números (y no necesariamente hashes, como estamos hablando aquí).
Orden natural
También conocido como: Native Byte Order, Internal Byte Order
Es el orden de los bytes tal como salen de la función hash. Es el orden que usas cuando te refieres a un bloque anterior en una cabecera de bloque bruta, o cuando te refieres a un TXID anterior en una transacción bruta.
En otras palabras, este es el orden de bytes con el que trabajan los programadores de Bitcoin cuando tratan con datos brutos.
Hash de Bloque: 6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000
Hash de Transacción: 169e1e83e930853391bc6f35f605c6754cfead57cf8387639d3b4096c54f18f4
Este orden de bytes a veces se llama "little-endian".
Orden inverso
También conocido como: RPC Byte Order, Network Byte Order
Es el orden inverso de los bytes tal como salen de la función hash. Es el orden que usas cuando buscas transacciones/bloques en exploradores, o cuando haces peticiones RPC al Bitcoin Core.
En otras palabras, este es el orden de bytes que ven y usan los usuarios de Bitcoin.
Hash de Bloque: 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f
Hash de Transacción: f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16
Este orden de bytes a veces se llama "big-endian".
¿Dónde usamos el orden natural?
Usas el orden natural al trabajar con datos brutos de Bitcoin internamente.
Por ejemplo, dentro de transacciones brutas, cada entrada se refiere a la salida de una transacción anterior usando el hash de esa transacción (el TXID), en el orden natural. Si buscas c997a5e56e104102fa209c6a852dd90660a20b2d9c352423edce25857fcd3704 en un explorador, no encontrarás nada. Pero, si inviertes el orden de los bytes a 0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9, lo encontrarás.
Lo mismo ocurre con el hash del bloque anterior dentro de una cabecera de bloque, e incluso con la raíz de Merkle (que también aparece invertida en los exploradores).
¿Por qué invertir el orden de los bytes?
Solo Satoshi sabe por qué, y nunca explicó su razonamiento.
Así que aquí va mi teoría sobre por qué invertimos el orden de los bytes de hashes de bloque y de transacción…
Hashes de Bloque
Los bytes que salen de una función hash no tienen significado. Son solo bytes aleatorios.
Al programar Bitcoin, Satoshi tuvo la idea ingeniosa de interpretar los hashes de bloque como enteros. Eso permite comparar el hash de bloque con un valor objetivo, y eso forma parte del mecanismo central de la minería.
Sin embargo, Satoshi estaba trabajando en una computadora con arquitectura little-endian. Eso significaba que, cuando esos hashes de bloque se convertían en enteros, la computadora leía los bytes de izquierda a derecha. Entonces, al ver los bytes del hash de bloque y del objetivo, quedaban "al revés", lo cual es molesto para los humanos, que estamos acostumbrados a ver el valor más grande a la izquierda (el orden big-endian).
Orden Natural (Little Endian):
Hash de Bloque (Bytes): 6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000
Objetivo (Bytes): 0000000000000000000000000000000000000000000000000000ffff00000000
Así que creo que Satoshi decidió invertir el orden de los bytes para hacer que los hashes de bloque fueran más amigables al mostrarlos:
Orden Inverso (Big-Endian):
Hash de Bloque (Bytes): 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f
Objetivo (Bytes): 00000000ffff0000000000000000000000000000000000000000000000000000
Mirando el hash de bloque ahora, puedes ver fácilmente que está por debajo del objetivo.
Así que, internamente, todos los hashes de bloque se tratan en el orden natural. Pero externamente, al hacer peticiones RPC o buscar en un explorador, los hashes van en el orden inverso, amigable para humanos.
RPC Byte Order. El orden inverso se usaba originalmente al hacer peticiones RPC al Bitcoin Core, y por eso a veces se llama RPC Byte Order.
TXIDs
Además de invertir el orden de los bytes de los hashes de bloque, Satoshi decidió invertir también el de los hashes de transacción (TXIDs):
Hash de Transacción: 169e1e83e930853391bc6f35f605c6754cfead57cf8387639d3b4096c54f18f4
TXID: f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16
No hay necesidad de hacer esto, ya que los hashes de transacción nunca se interpretan como enteros dentro de Bitcoin. Mi teoría es que Satoshi quiso consistencia al buscar bloques y transacciones: como los hashes de bloque ya se invertían, hizo lo mismo con las transacciones.
Por un accidente histórico, los hashes de tx y de bloque que usa Bitcoin Core tienen los bytes invertidos. No estoy totalmente seguro de por qué. Puede ser algo como usar el bignum de openssl para almacenar hashes, y luego imprimirlos como número.
¿Por qué no cambiarlo?
Sería mucho más simple, desde la perspectiva de un desarrollador, no tener que lidiar con la inversión de bytes todo el tiempo.
Sin embargo, esa configuración fue adoptada por tantos exploradores y carteras durante tanto tiempo que está demasiado arraigada para cambiarla. Es más fácil que los nuevos desarrolladores se acostumbren a esta peculiaridad que hacer que todo el mundo cambie la forma en que busca transacciones y bloques.
Mi queja, relacionada con la endianness, es tratar los hashes como números e invertirlos. Un hash es una secuencia de bytes con un orden bien definido, y no tiene más sentido invertirlo que invertir un texto.
¿Cómo invertir el orden de los bytes?
Aquí van algunos ejemplos de código:
# cadena hexadecimal
hash = "6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000"
# invierte el orden de los bytes
reversed = hash.scan(/../).reverse.join
puts reversed #=> 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f // cadena hexadecimal
$hex_string = "6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000";
// invierte el orden de los bytes
$reversed = implode('', array_reverse(str_split($hex_string, 2)));
echo $reversed; //=> 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f Si estás trabajando con cadenas, recuerda que un byte son 2 caracteres hexadecimales (por ejemplo, 6f). Así que no cometas el error de invertir simplemente la cadena completa; necesitas invertir el orden de los bytes.
Resumen
Usamos dos órdenes de bytes distintos para hashes de transacción y de bloque en Bitcoin:
- Orden Natural. Es lo que obtienes de la función hash al hashear los datos de transacción y las cabeceras de bloque. Se usa internamente, al trabajar con datos brutos, para referirse a transacciones y bloques anteriores.
- Orden Inverso. Son los mismos hashes, pero con el orden de los bytes invertido. Usamos este orden al buscar transacciones y bloques en exploradores.
Así que, al trabajar con datos de Bitcoin, usas los bytes tal como salen de la función hash. Pero, al mostrar hashes de bloque y de transacción como cadenas hexadecimales, necesitas invertir el orden de los bytes.
Todos nos hemos confundido con esto en algún momento, pero forma parte de la diversión de trabajar con datos brutos de Bitcoin.