Bitcon1O1

Protocolo Bitcoin: Transacciones

Bitcoin1o1

2022/10/16

El protocolo de Bitcoin está integrado por diversos componentes, uno de los principales, para comenzar a entender Bitcoin, son las transacciones. En un post anterior explicamos de forma general la estructura y funcionamiento de las Transacciones. Aquí entraremos a mayor detalle para entender como son y funcionan las transacciones a nivel del protocolo.

El contenido de este post esta basado en el trabajo de niftynei y su curso Bitcoin Protocol Deep Dive: Transactions, así como también en el contenido del libro de Programming Bitcoin de Jimmy Song.

Hay dos temas que es importante conocer antes de entrar de lleno al protocolo, estos son codificación y endianess (orden en el que se almacenan los bytes).

En este post haremos una muy breve introducción a ambos:

Codificación

La codificación se refiere a la base en la cual los datos se encuentran empacados, y define el conjunto de caracteres para cada espacio en una cadena para dicha representación. Por ejemplo una cadena de de 8 caracteres en código binario(equivalente a un Byte) y puede representar hasta 256 valores distintos.

Codificación Binaria

Codificación Base 10

Codificación hexadecimal

Ejemplos:

a)
    Codificación base 10 de 255:              255 
    Codificación base  2 de 255:         11111111
    Codificación base 16 de 255:               ff
b)
    Codificación base 10 de 255:            65535 
    Codificación base  2 de 255: 1111111111111111
    Codificación base 16 de 255:               ff

Como podemos ver, el objetivo de codificar en base 16 (hexadecimal) es poder representar números mas grandes, con menos caracteres.

Todas las computadoras que conocemos funcionan con Bits(la unidad mínima de información que se compone de 0 y 1), sin embargo la unidad mas común utilizada son los Bytes, que están compuestos de 8 bits cada uno. Un Byte puede representarse en decimal desde el número 0 hasta el número 255, lo que nos permite representar hasta 256 valores distintos. Del mismo modo en hexadecimal podemos representar el mismo Byte con únicamente 2 posiciones o caracteres. Desde 00, hasta ff.

Endianess

Endianess se refiere al orden en el que los datos son almacenados en la memoria de los sistemas de cómputo, en particular nos referimos a los Bytes. Existen primordialmente dos tipos de endianess:

Transacciones

Para trabajar con las transacciones utilizaremos Bitcoin en modo regtest y para hacernos la vida mas fácil empezaremos por crear un par de aliases en la línea de comando, e iniciaremos nuestro nodo.

> # creamos el alias para bitcoind en modo regtest y que inicie como demonio
> alias btcreg = 'bitcoing -regtest -daemon' 
> # creamos el comando para llamar al cliente siempre utilizando el modo regtest
> alias clireg = 'bitcoin-cli -regtest'
> # iniciamos bitcoing en modo regtest
> btcreg

En este post nos centraremos en lo que se denomina transaciones a direcciones legacy, en un futuro post, hablaremos de Segregated witness y de transacciones segwit.

Las transacciones de Bitcoin se serializan en base a Bytes y como veremos a continuación, algunos datos se serializan en Big endian y otros en Little endian.

Como vimos en el post Transacciones, la estructura de datos de cada transacción se compone primordialmente de entradas y salidas, tal como se muestra en la siguiente imagen.

Transacciones Bitcoin

Transacciones Bitcoin

Es importante notar que las entradas y salidas pueden ser múltiples, como se ve en el caso de la Tx3. Con ello también se pueden lograr consolidaciones (convertir varias salidas en una sola salida), así como dispersiones (convertir una entrada en múltiples salidas).De esta forma se pueden crear transacciones para consolidar UTXOs (Salidas de transacciones no gastadas) o dividir UTXOs. Si tienes dudas de como funcionan y que son los UTXOs por favor revisa el post de Transacciones.

Cada transacción de Bitcoin lleva un script de desbloqueo (scriptSig) que se utiliza para desbloquear los bitcoins de la salida de la transacción anterior y un script de bloqueo (scriptPubkey) que bloquea los bitcoins de esta transacción. Por el momento dejaremos de lado la explicación del funcionamiento de este mecanismo de desbloqueo/bloqueo y lo veremos es un post específico de Script, el lenguaje de smart contracts de Bitcoin.

La tarifa que se paga a los mineros por procesar la transacción es la diferencia de sumar el monto total de las entradas y restarle el monto total de las salidas. Debido a esto la suma de los montos de las entradas siempre debe ser mayor o igual a la suma del monto de las salidas, de echo es mejor que sea mayor, ya que un monto igual significaría que la comisión es cero y no quisiéramos, ya que es poco probable que un minero quiera procesar una transacción con tarifa 0.

Vamos a construir una transacción desde cero. Para ello necesitamos una salida de una transacción anterior, vamos a crearla. Primero necesitamos una dirección legacy, ya que por ahora solo veremos este tipo de transacción, después vamos a generar 101 bloques para que Bitcoin nos genere y libere la recompensa de una coinbase transaction de 50 bitcoins, por último vamos a enviar un bitcoin a nuestra nueva dirección legacy.

> # generamos una nueva dirección legacy
> clireg getnewaddress "" legacy
> mmfztm9sVUNm6K93HHvdGsWfimkhniu9aE
> # generamos 101 bloques para obtener la recompensa de la transacción coinbase
> clireg generatetoaddress 101 mmfztm9sVUNm6K93HHvdGsWfimkhniu9aE
> # 101 bloques generados
  "38e44ff988f659f0d75b4ba666b4f12e125f2008a8876bd737637c4b77e9b216",
  "4629cfbc448ad1ff0896d08bd32669d907e1f6c8b6bc2c78c4449306c7ab0edf",
  "399a008ae213c8d07c1bd7783238ffbf46d6cbafb15aee7878fc6c582511984c",
  ...
  "1e7c098e1c90a890da44fee9ccc925743b32a3229a6fdf9bef0b25383e9ade38",
  "3eeb905fd42cfa2ef2617bbd09bb91a60ebf52aae37a5d4f53f336baaab417e8",
  "126fb74525c7921a7972ea833e202b07724dda59c4480df63949ab0f56e2a39a"
> # enviamos un bitcoin a nuesra dirección y nos devuelve el txid de la transacción
> clireg sendtoaddress mmfztm9sVUNm6K93HHvdGsWfimkhniu9aE 1
dce07ed2f10758d902ab16a804ff745f31edf4858de07a26150901c513cc2b0a

> # inspeccionemos la transacción con getrawtransaction indicando true en 
> # el campo verbose para que nos devuelva la tx en JSON
> clireg getrawtransaction dce07ed2f10758d902ab16a804ff745f31edf4858de07a26150901c513cc2b0a true

{
  "txid": "dce07ed2f10758d902ab16a804ff745f31edf4858de07a26150901c513cc2b0a",
  "hash": "7aa2c1b500836dee71339fc713af6cf13cbcc691dcf34e40ade2bbf02f65c816",
  "version": 2,
  "size": 228,
  "vsize": 147,
  "weight": 585,
  "locktime": 303,
  "vin": [
    {
      "txid": "25306d130996c232cb20ba349c87f51e955371a014374b77537070d83aaad2c5",
      "vout": 0,
      "scriptSig": {
        "asm": "",
        "hex": ""
      },
      "txinwitness": [
        "3044022009b480d0ff59ee9d8cd6eb00bc3bb789a28cd16e9d080d7293531ed6028eb0b902207d06cbe8b71e1206d6109b8d99ba2eaeb95a79ee5619f16af2ffcff9ff81512b01",
        "02a650de7105797ac083a98b1b2da2d4203d6f48c6791ad17cc3e256005c9e8ef9"
      ],
      "sequence": 4294967293
    }
  ],
  "vout": [
    {
      "value": 1.00000000,
      "n": 0,
      "scriptPubKey": {
        "asm": "OP_DUP OP_HASH160 4386aadb20ec57ecf91b7a868d966b1395d07945 OP_EQUALVERIFY OP_CHECKSIG",
        "desc": "addr(mmfztm9sVUNm6K93HHvdGsWfimkhniu9aE)#azjzcf78",
        "hex": "76a9144386aadb20ec57ecf91b7a868d966b1395d0794588ac",
        "address": "mmfztm9sVUNm6K93HHvdGsWfimkhniu9aE",
        "type": "pubkeyhash"
      }
    },
    {
      "value": 46.99991420,
      "n": 1,
      "scriptPubKey": {
        "asm": "OP_DUP OP_HASH160 dfd9c03b021e23e4043e135729133cbfaa99cfaa OP_EQUALVERIFY OP_CHECKSIG",
        "desc": "addr(n1vZqRtnYTg5WVNcPgQn2cEZmAzCJFvgYo)#ecw2een5",
        "hex": "76a914dfd9c03b021e23e4043e135729133cbfaa99cfaa88ac",
        "address": "n1vZqRtnYTg5WVNcPgQn2cEZmAzCJFvgYo",
        "type": "pubkeyhash"
      }
    }
  ],
  "hex": "02000000000101c5d2aa3ad8707053774b3714a07153951ef5879c34ba20cb32c29609136d30250000000000fdffffff0200e1f505000000001976a9144386aadb20ec57ecf91b7a868d966b1395d0794588ac7c2d2418010000001976a914dfd9c03b021e23e4043e135729133cbfaa99cfaa88ac02473044022009b480d0ff59ee9d8cd6eb00bc3bb789a28cd16e9d080d7293531ed6028eb0b902207d06cbe8b71e1206d6109b8d99ba2eaeb95a79ee5619f16af2ffcff9ff81512b012102a650de7105797ac083a98b1b2da2d4203d6f48c6791ad17cc3e256005c9e8ef92f010000"
}

Revisando la transacción podemos ver que la salida número 0 es la que está denominada por el monto de 1 bitcoin, es esta salida la que vamos a utilizar para nuestra nueva transacción. Toda transacción indica en sus entradas, la salida anterior de donde se van a tomar los bitcoins para la nueva transacción. En este caso los bitcoins se van a tomar de la salida 0, de la transacción: dce07ed2f10758d902ab16a804ff745f31edf4858de07a26150901c513cc2b0a.

Deseamos hacer una nueva transacción por un monto de 1 bitcoin, menos 1,000 satoshis. Esto significa que queremos pagar una tarifa de 1,000 satoshis a los mineros por procesar nuestra transacción. Todos los campos se tienen que codificar en hexadecimal (base 16).

Si pensamos en las transacciones como un formulario, en la siguiente tabla podemos ver los campos que tendría una transacción legacy. Por el momento el campo de versión lo dejaremos como 1 que es lo mas común, el campo de No. de inputs es 01 ya que únicamente utilizaremos una entrada, la entrada a utilizar ya la definimos como la salida 0 de la transacción que inspeccionamos, el campo de desbloqueo (scriptSig) lo dejaremos en longitud 00, mas adelante veremos por qué. El campo sequence por lo general es ffffffff, en los outputs especificamos un único output 01, con nuestro monto en hexadecimal, en el campo de bloqueo de nuestra transacción (scriptPubkey)lo llenaremos pero no veremos por ahora que significa, el último campo locktime sirve para retrasar temporalmente una transacción, pero por el momento lo dejaremos en 00000000.

Campo Descripción
Version versión de la transacción, por lo general 1
No. de entradas Número de entradas, pueden ser varias
txid txid de la salida anterior a utilizar
vout Número de la salida anterior
scriptSig Script de desbloqueo de la salida anterior
sequence por el momento lo dejaremos como ffffffff
No. de saldas Número de salidas, pueden ser varias
Amount Monto de la transacción
scriptPubKey Script de bloqueo
locktime por ahora usaremos 00000000, se utiliza para retrasar transacciones

Ahora podemos ver como queda nuestro formulario (en los campos en los que no se especifica endianess se asume Big endian).

Para convertir los valores de decimal a hexadecimal y para invertir las cadenas a Little endian, podemos usar Python en la línea de comandos. Los montos en las transacciones están denominados en satoshis, 1 bitcoin = 100,000,000 (cien millones de satoshis).

> python3
>>> amount = 100_000_000 - 1_000
>>> amount
99999000
>>> amount_in_hex = hex(99999000)
>>> amount_in_hex
'0x5f5dd18'
>>> bytes_hex = bytes.fromhex('05f5dd18')
# invertir la lista de bytes
>>> bytes_hex[::-1].hex()
'18ddf505'
>>> tx_id_bytes = bytes.fromhex('dce07ed2f10758d902ab16a804ff745f31edf4858de07a26150901c513cc2b0a')
# invertir la lista de bytes
tx_id_bytes[::-1].hex()
'0a2bcc13c5010915267ae08d85f4ed315f74ff04a816ab02d95807f1d27ee0dc'
Etiquetas:
    Campos de longitud fija:    0000 0000
    Cuenta de inputs/Outputs:   00
    Tamaño de campo variable:   00
    Campos variables:           0000

Veamos como quedaría nuestro formulario con los datos, tanto los que van en big endian, como los que van en little endian.

NOTA: puedes usar el mouse posicionándote en cada campo para ver que campo es:

Tx:


VERSION, 4 bytes little endian:      01 00 00 00
INPUTS:                              01
    TXID, 32 bytes little endian:    0a2bcc13c5010915267ae08d85f4ed315f74ff04a816ab02d95807f1d27ee0dc
    VOUT,  4 bytes little endian:    00 00 00 00
    SCRIPTSIG, (unlocking script):   00
    SEQUENCE, 4 bytes little endian: fe ff ff ff
OUTPUTS:                             01
    AMOUNT, 8 bytes little endian:   18 dd f5 05 00 00 00 00
    SCRIPTPUBKEY, (locking script):  19 76a914dfd9c03b021e23e4043e135729133cbfaa99cfaa88ac
LOCKTIME, 4 bytes little endian:     00 00 00 00


Si empaquetamos los campos para formar un solo string de Bytes, esta es la forma en la que las transacciones se procesan en Bitcoin, nuestra transacción quedaría de la siguiente forma, NOTA: puedes usar el mouse posicionándote en cada sección de la transacción para ver que campo es:




01000000010a2bcc13c5010915267ae08d85f4ed315f74ff04a816ab02d95807f1d27ee0dc0000000000feffffff
0118ddf505000000001976a914dfd9c03b021e23e4043e135729133cbfaa99cfaa88ac00000000



Ahora necesitamos ver si codificamos bien nuestra transacción.



> # Decodificamos nuestra transacción
> clireg decoderawtransaction 01000000010a2bcc13c5010915267ae08d85f4ed315f74ff04a816ab02d95807f1d27ee0dc
0000000000feffffff0118ddf505000000001976a914dfd9c03b021e23e4043e135729133cbfaa99cfaa88ac00000000

Así se ve nuestra transacción decodificada:

{
  "txid": "346e99cb20d3c751a01e08d18b0eeeee3a0708da2bcc87c8a496b1d1f259e63d",
  "hash": "346e99cb20d3c751a01e08d18b0eeeee3a0708da2bcc87c8a496b1d1f259e63d",
  "version": 1,
  "size": 85,
  "vsize": 85,
  "weight": 340,
  "locktime": 0,
  "vin": [
    {
      "txid": "dce07ed2f10758d902ab16a804ff745f31edf4858de07a26150901c513cc2b0a",
      "vout": 0,
      "scriptSig": {
        "asm": "",
        "hex": ""
      },
      "sequence": 4294967294
    }
  ],
  "vout": [
    {
      "value": 0.99999000,
      "n": 0,
      "scriptPubKey": {
        "asm": "OP_DUP OP_HASH160 dfd9c03b021e23e4043e135729133cbfaa99cfaa OP_EQUALVERIFY OP_CHECKSIG",
        "desc": "addr(n1vZqRtnYTg5WVNcPgQn2cEZmAzCJFvgYo)#ecw2een5",
        "hex": "76a914dfd9c03b021e23e4043e135729133cbfaa99cfaa88ac",
        "address": "n1vZqRtnYTg5WVNcPgQn2cEZmAzCJFvgYo",
        "type": "pubkeyhash"
      }
    }
  ]
}

Podemos ver claramente que consta de un input que viene de la transacción dce07ed2f10758d902ab16a804ff745f31edf4858de07a26150901c513cc2b0a, del input No. 0, y de un output, también el número cero, por el valor de 0.99999000, aquí este monto, en el JSON, está especificado en bitcoins y no en satoshis.

Para poder hacer un broadcast de nuestra transacción primero tenemos que tener los datos del campo que desbloquea los bitcoins que usamos en nuestra entrada (el scriptSig), y que vienen de la transacción anterior, para ello le vamos a pedir a nuestra billetera que firme la transacción y nos genere el scriptSig que se requiere. Como lo comentamos, el detalle de los Scripts, lo veremos en un futuro post.


> # Le pedimos a nuestra billetera que firme nuestra transacción
> clireg signrawtransactionwithwallet 01000000010a2bcc13c5010915267ae08d85f4ed315f74ff04a816ab02d95807f1d27ee0dc
0000000000feffffff0118ddf505000000001976a914dfd9c03b021e23e4043e135729133cbfaa99cfaa88ac00000000

{
  "hex": "01000000010a2bcc13c5010915267ae08d85f4ed315f74ff04a816ab02d95807f1d27ee0dc000000006a47304402202aedf108faf6a5f662c3fa02b7ba55f0992e1c8c0579c829675ce615c95ece20022078e0f272115094aec231b2f5d8cd428447c758a6728a772db43334e0e79956e40121036869e1a4d4c1d446a28cf405581c6b7acfa47ed3b1579ca4306606366fad7cb9feffffff0118ddf505000000001976a914dfd9c03b021e23e4043e135729133cbfaa99cfaa88ac00000000",
  "complete": true
}

Podemos publicar nuesrtra transacción.

> clireg sendrawtransaction '01000000010a2bcc13c5010915267ae08d85f4ed315f74ff04a816ab02d95807f1d27ee0dc000000006a47304402202aedf108faf6a5f662c3fa02b7ba55f0992e1c8c0579c829675ce615c95ece20022078e0f272115094aec231b2f5d8cd428447c758a6728a772db43334e0e79956e40121036869e1a4d4c1d446a28cf405581c6b7acfa47ed3b1579ca4306606366fad7cb9feffffff0118ddf505000000001976a914dfd9c03b021e23e4043e135729133cbfaa99cfaa88ac00000000'

Y obtenemos el txid de nuestra transacción.

'ddc97853738b805d2ed4a98140cf232d6201c0875137bfb2470d6cc0f7bdc015'

Decodifiquemos en JSON nuestra nueva transacción para verificarla.

> clireg getrawtransaction 'ddc97853738b805d2ed4a98140cf232d6201c0875137bfb2470d6cc0f7bdc015' true

Asi quedaría nuestra transacción.

{
  "txid": "ddc97853738b805d2ed4a98140cf232d6201c0875137bfb2470d6cc0f7bdc015",
  "hash": "ddc97853738b805d2ed4a98140cf232d6201c0875137bfb2470d6cc0f7bdc015",
  "version": 1,
  "size": 191,
  "vsize": 191,
  "weight": 764,
  "locktime": 0,
  "vin": [
    {
      "txid": "dce07ed2f10758d902ab16a804ff745f31edf4858de07a26150901c513cc2b0a",
      "vout": 0,
      "scriptSig": {
        "asm": "304402202aedf108faf6a5f662c3fa02b7ba55f0992e1c8c0579c829675ce615c95ece20022078e0f272115094aec231b2f5d8cd428447c758a6728a772db43334e0e79956e4[ALL] 036869e1a4d4c1d446a28cf405581c6b7acfa47ed3b1579ca4306606366fad7cb9",
        "hex": "47304402202aedf108faf6a5f662c3fa02b7ba55f0992e1c8c0579c829675ce615c95ece20022078e0f272115094aec231b2f5d8cd428447c758a6728a772db43334e0e79956e40121036869e1a4d4c1d446a28cf405581c6b7acfa47ed3b1579ca4306606366fad7cb9"
      },
      "sequence": 4294967294
    }
  ],
  "vout": [
    {
      "value": 0.99999000,
      "n": 0,
      "scriptPubKey": {
        "asm": "OP_DUP OP_HASH160 dfd9c03b021e23e4043e135729133cbfaa99cfaa OP_EQUALVERIFY OP_CHECKSIG",
        "desc": "addr(n1vZqRtnYTg5WVNcPgQn2cEZmAzCJFvgYo)#ecw2een5",
        "hex": "76a914dfd9c03b021e23e4043e135729133cbfaa99cfaa88ac",
        "address": "n1vZqRtnYTg5WVNcPgQn2cEZmAzCJFvgYo",
        "type": "pubkeyhash"
      }
    }
  ],
  "hex": "01000000010a2bcc13c5010915267ae08d85f4ed315f74ff04a816ab02d95807f1d27ee0dc000000006a47304402202aedf108faf6a5f662c3fa02b7ba55f0992e1c8c0579c829675ce615c95ece20022078e0f272115094aec231b2f5d8cd428447c758a6728a772db43334e0e79956e40121036869e1a4d4c1d446a28cf405581c6b7acfa47ed3b1579ca4306606366fad7cb9feffffff0118ddf505000000001976a914dfd9c03b021e23e4043e135729133cbfaa99cfaa88ac00000000"
}

Por último podemos minar un bloque para que nuestra transacción se procese.

> clireg generatetoaddress 1 'mmfztm9sVUNm6K93HHvdGsWfimkhniu9aE'

Y revisar que nuestra transacción se encuentra en la lista de UTXOs

> clireg listunspent
{
    "txid": "ddc97853738b805d2ed4a98140cf232d6201c0875137bfb2470d6cc0f7bdc015",
    "vout": 0,
    "address": "n1vZqRtnYTg5WVNcPgQn2cEZmAzCJFvgYo",
    "scriptPubKey": "76a914dfd9c03b021e23e4043e135729133cbfaa99cfaa88ac",
    "amount": 0.99999000,
    "confirmations": 1,
    "spendable": true,
    "solvable": true,
    "desc": "pkh([2823aaf7/44'/1'/0'/1/1]0375127145792370c8c1a94d6298b19182de6b027291bf0f8df525a068123118bc)#tw5zv4ce",
    "parent_descs": [
      "pkh(tpubD6NzVbkrYhZ4YskC1QDb6eSy4z3tPHk3fPAyZWWggAFzFEApktGZf3CQTt8ZPszTd2C4QNqCkjZPcaC1ZXfwkqaq2UfyuQqQHChSpmV687P/44'/1'/0'/1/*)#w0k83g5r"
    ],
    "safe": true
  }

Excelente, hemos creado exitosamente una transacción desde cero y codificándola en crudo (hexadecimal), la hemos publicado, minado y verificado el el conjunto de UTXOs, utilizando el comando listunspent.

El siguiente paso es entender bien como funciona Script y el cambio a Segregated Witness, que marcó un hito muy importante en la historia de Bitcoin.