Lo que aprendimos sobre Timelocks y Miniscript con Fundy

Iniciamos temporada con un proyecto (aún en fase de producción) made in Málaga → Fundy: Una wallet Bitcoin que implementa timelocks y miniscripts como medidas adicionales de seguridad para proteger fondos. Estas opciones avanzadas permiten establecer condiciones específicas para que únicamente se liberen los fondos bajo determinadas circunstancias.
Lo que aprendimos sobre Timelocks y Miniscript con Fundy

Juan Carlos De La Torre y Diego García, desarrolladores de Fundy, nos presentaron su proyecto con ilusión y entusiasmo contagioso. Estaremos muy atentos a su evolución y esperamos estar “diseñando recetas” con Fundy muy pronto.

Mientras tanto puedes leer su White Paper aquí.

Durante su exposición aprendimos cuales son las posibilidades y casos de uso de timelocks y miniscript. Veamos un poco más en detalle en qué consiste cada uno de estos conceptos.

Timelocks

Se trata de un contrato inteligente que restringe el gasto de algunos UTXOs hasta un determinado momento futuro (medido en tiempo UNIX) o una altura de bloque. De ahí su nombre “bloqueo temporal”.

Realmente no son nada nuevo y fueron añadidos al software original de Bitcoin por Satoshi Nakamoto. Están presentes en todas las transacciones aunque la mayoría no utilice esta función, por lo que el tiempo de bloqueo por defecto es 0x00000000 (0) o 0xFFFFFFFF (4294967295).

CLASIFICACIÓN ↓→ Absoluto Relativo
A nivel de transacción nLockTime nSequence
A nivel de Script CheckLockTimeVerify (CLTV) CheckSequenceVerify (CSV)

Atributos clave de los Timelocks

Los Timelocks en Bitcoin tienen tres atributos comunes: Extraido de la presentación de Fundy

UBICACIÓN

Los timelocks pueden incluirse en las transacciones además de en los scripts.

Un timelock a nivel de transacción (nLockTime y nSequence) no se validará hasta un momento determinado. Esto se aplica incluso si la firma es válida.

Los timelocks a nivel de transacción sólo evitan que la transacción sea transmitida/minada, pero las salidas pueden ser doblemente gastadas antes de que llegue el momento establecido en el timelock. Un ejemplo extraído de Mastering Bitcoin:

Alice firma una transacción gastando una de sus salidas a la dirección de Bob, y configura la transacción nLocktime dentro de 3 meses. Alice envía esa transacción a Bob para que la guarde. Con esta transacción Alice y Bob saben que:

  • Bob no puede transmitir la transacción para disponer de los fondos hasta que hayan transcurrido 3 meses.
  • Bob podrá transmitir la transacción después de 3 meses.

Sin embargo:

  • Alice puede crear otra transacción, gastando dos veces las mismas entradas sin tiempo de bloqueo. Por lo tanto, Alice puede gastar el mismo UTXO antes de que transcurran los 3 meses.
  • Bob no tiene ninguna garantía de que Alice no lo haga

Este problema del doble gasto es una limitación de nLockTime. La única garantía es que Bob no dispondrá de estos fondos antes de que pasen 3 meses e incluso entonces, no hay garantía de que Bob obtenga los fondos. Tal garantía sólo se puede obtener a través de un timelock a nivel de script que restrinja el gasto del UTXO, en lugar de en la transacción.

Los timelocks a nivel de script, como CheckLockTimeVerify (CLTV) y CheckSquenceVerify (CSV), determinan si una transacción se puede realizar o no.

Recapitulando:

⏱️ Una transacción con bloqueo de tiempo a nivel de script es válida antes de que se alcance el bloqueo de tiempo, simplemente no se puede gastar el UTXO.

⏱️ Los bloqueos de tiempo a nivel de transacción invalidan las transacciones hasta que se llegue al momento establecido.

ORIENTACIÓN

Existen timelocks de tiempo absoluto o de tiempo relativo. En ambos se especifica un momento concreto en el futuro en el que debe validarse una transacción.

Timelock absoluto: Determina una marca de tiempo específica o altura de bloque hasta el que la salida de una transacción es inválida. (Por ejemplo: a las 22:00)

Timelock relativo: Los bloqueos relativos hacen que las salidas de una transacción no se puedan gastar hasta que haya transcurrido una cantidad determinada de tiempo/bloques desde que la anterior salida de la transacción se minó en un bloque. (Por ejemplo: en 6 horas)

MÉTRICA

En Bitcoin existe dos formas de medir el tiempo: el número de bloque y la marca de tiempo, por lo que podemos utilizar ambos para establecer un timelock. Cuando se establece un timelock en base a un número de bloque, los mineros deben esperar alcanzar dicho número de bloque para poder validar y confirmar la operación, e incluirla en un nuevo bloque.

Al establecer el timelock en base a una marca de tiempo, los mineros esperan a que transcurra el tiempo establecido en segundos. Es decir, que se alcance un momento determinado para hacer la validación de la transacción. Esto se mide con la marca de tiempo Unix.

Tipos de Bloqueo

nLockTime

Se trata de un bloqueo de tiempo absoluto a nivel de script. Fue el primero en ser implementado por Satoshi Nakamoto en el software original de bitcoin. Se puede encontrar aquí.

Cuando se utiliza este parámetro en una transacción, ésta permanece inválida hasta que transcurre el tiempo especificado.

Así sería la estructura de una transacción Bitcoin en la que el tiempo de bloqueo está establecido en 0:

{
  "version": 1,
  "locktime": 0,
  "vin": [
    {
      "txid": "7957a35fe64f80d234d76d83a2a8f1a0d8149a41d81de548f0a65a8a999f6f18",
      "vout": 0,
      "scriptSig" : "3045022100884d142d86652a3f47ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb02204b9f039ff08df09cbe9f6addac960298cad530a863ea8f53982c09db8f6e3813[ALL] 0484ecc0d46f1918b30928fa0e4ed99f16a0fb4fde0735e7ade8416ab9fe423cc5412336376789d172787ec3457eee41c04f4938de5cc17b4a10fa336a8d752adf",
      "sequence": 4294967295
    }
  ],
  "vout": [
    {
      "value": 0.01500000,
      "scriptPubKey": "OP_DUP OP_HASH160 ab68025513c3dbd2f7b92a94e0581f5d50f654e7 OP_EQUALVERIFY OP_CHECKSIG"
    },
    {
      "value": 0.08450000,
      "scriptPubKey": "OP_DUP OP_HASH160 7f9b1a7fb68d60c536c2fd8aeaa53a8f3cc025a8 OP_EQUALVERIFY OP_CHECKSIG",
    }
  ]
}

Si el nLockTime especificado es inferior a 500 millones, el parámetro se interpreta como la altura del bloque, lo que significa que la transacción sólo se valida cuando se alcanza un número de bloque específico; de lo contrario, si el valor es superior a 500 millones, se interpreta como una marca de tiempo UNIX, en cuyo caso, la transacción permanece inválida hasta que ha transcurrido una marca de tiempo específica.

🗓️ nLockTime puede bloquear una transacción hasta 9.500 años utilizando números de bloque y 2.106 años utilizando marcas de tiempo. En la mayoría de los monederos, este valor suele ser 0, lo que indica que la transacción se valida inmediatamente después de su construcción.

nSequence

nSequence es un campo que puede utilizarse para un bloqueo temporal relativo a nivel de transacción, especificando el tiempo mínimo de espera hasta que una entrada puede añadirse a un bloque dependiendo de hace cuánto tiempo se minó la salida anterior.

Además, nSequence nos permite añadir varias condiciones temporales diferentes dentro de una misma transacción. Así, una transacción sólo será válida si se cumplan todas las condiciones.

CheckLockTimeVerify (CLTV):

OP_CLTV añade un obstáculo en el script de bloqueo haciendo que los UTXO (monedas) no se puedan gastar hasta que haya transcurrido el tiempo especificado en nLocktime.

También permite a los usuarios cambiar los parámetros de autenticación de las direcciones de wallets multifirma. Tomando como ejemplo una dirección de wallet multifirma 2-de-3, CLTV se puede utilizar para cambiar el parámetro cuando se cumplan las condiciones a 1-de-3. En este caso, una persona puede recuperar fondos en las condiciones predefinidas especificadas en la transacción.

Ejemplo de OP_CLTV: Alice y Bob crean cada uno su propio par de claves. Después Bob crea el siguiente script:

IF
    <now + 6 months> CHECKLOCKTIMEVERIFY DROP
    <Bob’s pubkey> CHECKSIGVERIFY
ELSE
    <Alice’s pubkey> CHECKSIGVERIFY
ENDIF

Después de crear el script, Bob lo hashea, lo codifica como una dirección P2SH y lo envía a la dirección a Alice. Si Alice envía bitcoin a esta dirección, puede canjearlo con el siguiente scriptSig: 0 <Alice’s signature> 0

Una vez transcurridos los 6 meses, Bob también puede disponer de los fondos con el script: 0 <Bob’s signature> 1

CheckSquenceVerify (CSV):

OP_CSV hace que el UTXO no se pueda gastar hasta que haya transcurrido una cierta cantidad de tiempo/bloques en relación con el momento en que se minó el UTXO en cuestión. Por ejemplo, si OP_CSV se establece en 65 bloques, el UTXO sólo se podrá gastar cuando haya transcurrido un tiempo estimado de 65 bloques desde que se minó el UTXO.

CheckSequenceVerify se utiliza en la capa 2 de Bitcoin (Lightning Network) porque permite encadenar múltiples cadenas de transacciones. Este script da la posibilidad de establecer una fecha de caducidad relativa a la primera transacción emitida, por lo que se puede crear una cadena de transacciones manteniendo las garantías de timelock.

Ejemplo de OP_CSV: Una fianza (escrow) que caduca automáticamente 60 días después de ser depositada: Alice, Bob y un servicio de custodia crean un multisig 2-de-3 con el siguiente script:

IF
        2 <Alice's pubkey> <Bob's pubkey> <Escrow's pubkey> 3 CHECKMULTISIG
    ELSE
        “60d” CHECKSEQUENCEVERIFY DROP
        <Alice's pubkey> CHECKSIG
    ENDIF

Nicolas Dorier (BTCPay Server) comparaba CLTV y CSV de la siguiente manera:

  • Con CheckSequenceVerify puedes decir: Quiero que esta salida sea gastable cuando tenga X confirmaciones (relativa al tiempo de minado)
  • Con CheckLocktimeVerify puedes decir: Quiero que esta salida sea gastable a la altura de bloque X (tiempo absoluto)

Miniscript

Cada vez que transaccionamos bitcoin le estamos indicando a la wallet que ejecute un comando con algunos parámetros como dirección a la que quieres enviar, la cantidad que quieres enviar, el UTXO que deseas utilizar, la tarifa que se está dispuesto a pagar a los mineros para asegurar la transacción y, por supuesto, una firma válida de tu clave privada.

Para ello, la wallet utiliza scripts establecidos u OP_CODES nativos de la red bitcoin. Esta lista de instrucciones aceptadas por la red bitcoin y registradas con cada transacción describe cómo la siguiente persona que quiera gastar los bitcoin que se transfieren puede acceder a ellos. Esto es lo que se conoce como script.

La mayoría de nosotros no tenemos que lidiar con esos comandos gracias a que los desarrolladores han diseñado aplicaciones y dispositivos que garantizan una experiencia fluida y segura.

Los Miniscripts son un lenguaje de programación simplificado diseñado para crear scripts de Bitcoin reduciendo la cantidad de cálculos complejos. Esto ayuda a reducir el riesgo de errores y los hace ideales para aplicaciones personalizadas y contratos inteligentes más avanzados en la red de Bitcoin.

Casos de uso de Miniscript

Miniscript abre la posibilidad a distintos usos que hasta ahora eran impensables (por la complejidad que supondría programar en BitcoinScript). Veamos algunos de ellos:

Multifirma con decadencia:

Se podría crear una cartera multi-firma que requiera un 3 firmas de 5 para desbloquear los fondos, pero a medida que pasa el tiempo o se alcanza un tiempo de bloque determinado, esa multi-firma se convierta en una 2 de 3 permitiendo menos claves para mover fondos, en caso de que algunas partes de la multi-firma hayan perdido el acceso a sus respectivas claves.

Herencia programada:

Otra de las posibilidades que habilita miniscript es la creación de una cartera de herencia donde se especifica que una de las firmas de una cartera multi-firma pueda disponer de los fondos después de un cierto tiempo o de una cantidad determinada de bloques desde que los fondos fueron trasladados a esa cartera.

Apertura retardada:

Si la cartera de un usuario se ve comprometida, éste puede recurrir a la estrategia del “botón del pánico” para proteger sus fondos. Esto implica trasladar rápidamente los fondos a un almacenamiento en frío ultraseguro, al que debe ser muy difícil acceder, antes de que expire el bloqueo temporal, impidiendo el acceso no autorizado. Esta medida de último recurso, garantiza la seguridad de los fondos, ya que el atacante es prácticamente incapaz de acceder a ellos, aunque también será bastante incómodo para el usuario. Esta opción de almacenamiento podría ser una dirección Bitcoin alternativa gestionada por una clave privada almacenada de forma segura en una cámara acorazada remota en otro país, por ejemplo, accesible en última instancia para el usuario pero que suponga un obstáculo insalvable para el atacante.

Descriptores

Un descriptor es una representación legible de cómo se pueden gastar los UTXOs de tu wallet. Un descriptor que soporta miniscript es aquel que permite añadir de forma sencilla condiciones programables como timelocks (bloqueos temporales), multifirma, …

Recursos para saber más

Si quieres seguir profundizando en esta madriguera te recomendamos este podcast de Lunaticoin con José Luís Landabaso, autor de la librería BitcoinerLab en la que Fundy se han basado para su proyecto:

Incluso puedes probar a crear tu propia demo de wallet miniscript con BitcoinerLab, que es una librería de software que utiliza el lenguaje de programación Javascript, que te permite crear wallets que utilicen descriptores que soporten miniscript:

BitcoinerLAB

Si quieres probar una wallet que ya implementa miniscript, prueba con Liana:

Liana


Esperamos que os haya resultado útil. Si te ha quedado alguna duda o quieres corregir alguna inexactitud te leemos en los comentarios. 🧡


No comments yet.