Tapscript: nuevos códigos de operación, límites reducidos y convenios
Liquid Network Blockstream Research

Tapscript: nuevos códigos de operación, límites reducidos y convenios

Andrew Poelstra

Introducción

Esta es la tercera y última parte de nuestra serie de publicaciones dedicadas a las mejoras que implementamos en Liquid para acompañar el lanzamiento de Taproot. Las dos publicaciones anteriores abordaron Taproot en sí y las transacciones parcialmente firmadas de Elements. En esta oportunidad, vamos a describir los nuevos códigos de operación que incorporamos a Elements Script, para qué se usan y qué haremos con ellos.

Este conjunto de códigos de operación constituye la optimización más importante de Script desde el lanzamiento de Elements Alpha en 2015. Nuestro equipo de investigación también viene innovando la criptografía aplicada relacionada con Bitcoin, dado que se produjeron grandes avances respecto a MuSig, la blockchain de Elements y Lightning Network; además, seguimos trabajando en el scripting y en Simplicity. En concreto, aportamos muchísimo a Taproot en Bitcoin, y algunos de los cambios de Elements Script se derivan de nuestro trabajo, entre ellos los siguientes:

  • La eliminación de los límites numéricos fijos en el conteo de códigos de operación y los tamaños de los scripts;
  • El reemplazo de las firmas ECDSA con Schnorr;
  • El reemplazo de CHECKMULTISIG por CHECKSIGADD, que permite la verificación por lotes de múltiples firmas.
  • Numerosos códigos de operación de tipo OP_SUCCESS, que amplían el conjunto de códigos de operación que podemos introducir en la red mediante una birfucación menor (soft fork), comparados con los códigos de operación anteriores, de tipo NOP.

También hablaremos de los convenios, que le permiten a Script restringir los flujos de pago a fin de construir «contratos inteligentes» que imponen condiciones a las monedas y demás activos. A lo largo de los años, los convenios, que forman parte de Elements desde el principio, han sido objeto de numerosas y diversas propuestas para Bitcoin. Para crear Tapscript, partimos de la devolución de los programadores sobre nuestra implementación original de los convenios y les agregamos nuevos códigos de operación que incrementarán su expresividad, eficiencia y ergonomía.

Por último, le dedicaremos un breve segmento a Elements Miniscript, nuestra extensión del idioma Miniscript de Bitcoin, que emplea dichos códigos de operación para generar scripts compatibles con los convenios. Este proyecto está en una fase de desarrollo acelerado, y nos entusiasma ver cómo habilita nuevas aplicaciones en Liquid. Más adelante, nos explayaremos más sobre este tema en una nueva publicación.

Convenios y activos

Originalmente, la propuesta de convenios para Bitcoin surgió de Greg Maxwell en 2013, aunque la planteó con mucha soltura: les pidió a los usuarios que pensaran aplicaciones inútiles y absurdas de dicho concepto. Desafortunadamente, esas malas ideas—por divertidas que sean como ejercicio—parecen haber opacado a los convenios como concepto hasta el día de hoy, a pesar de que las inquietudes que existen al respecto no son particularmente prácticas ni privativas de los convenios. Ese tipo de razonamiento centrado en el riesgo es uno de los motivos por los cuales los convenios siguen siendo un tema candente en el espacio Bitcoin.

La propuesta más reciente y más popular para los convenios en Bitcoin es OP_CTV, que reduce la funcionalidad adrede a fin de evitar potenciales controversias.

Desde nuestro punto de vista, dichos miedos son, en su mayoría, desproporcionados. Se desprenden de la idea de que los convenios podrían utilizarse para «corromper» los bitcoins prexistentes, por ejemplo, mediante la implementación de un nuevo requisito para usarlos, como la firma de terceros. En teoría, dicha modificación se diseminaría por todo el volumen circulante de monedas y sería imposible de eliminar. Pero quien quisiera implementar un plan de esa índole, ya podría hacerlo hoy mismo gracias a los scripts CHECKMULTISIG 2 de 2, donde una de las partes se rehúsa a firmar transacciones que no cumplan con la restricción (o «convenio») 2 de 2. Nadie lo hizo, y con razón: es obvio que los usuarios no aceptarían dichas monedas en lugar de las monedas comunes, sobre todo aquellos que tienen la obligación legal o fiduciaria de custodiar las monedas comunes. Asimismo, las monedas resultantes supondrían costos de red más altos para ser gastadas, debido a su peso mayor, e incrementarían la complejidad de las carteras necesarias para gastarlas.

Quien intentase hacer lo propio con los convenios se enfrentaría a costos todavía mayores y requeriría una cartera aún más compleja. Nadie querría implementarlo y los usuarios lo rechazarían.

Para subrayar el carácter inofensivo de los convenios, basta recordar que están presentes en Liquid Network desde su lanzamiento, en 2018, y nunca hubo casos de convenios «virales» que se diseminaran por todo el sistema. (De todos modos, como ya veremos, hay motivos técnicos que dan cuenta de la escasa adopción de los convenios en Liquid en general.)

Los convenios incorporarían nuevas funcionalidades a Bitcoin, como las bóvedas y los límites de velocidad, que les otorgarían a los usuarios mayor flexibilidad a la hora de custodiar sus monedas.

En Elements, que cuenta con recursos para los activos emitidos, la funcionalidad provista por los convenios es mucho mayor. Allí podemos:

  • Construir órdenes límite a término indefinido u otras operaciones algorítmicas.
  • Construir derivados financieros, como opciones.
  • Crear «tokens de control», es decir, NFT que habilitan funcionalidades en otros contratos.

En un escenario caracterizado por múltiples activos y una amplia gama de códigos de operación aritméticos, podríamos igualar el conjunto de características de Ethereum y otros competidores; además, al evitar un sistema Turing completo, deberíamos ser capaces de implementar formas de análisis robustas. Sin embargo, como ya veremos, la historia es un poco más compleja.

Elements y Bitcoin Script

Elements fue desarrollado en 2015 como una bifurcación de Bitcoin con características adicionales, y se lanzó como red de prueba, denominada Elements Alpha, ese mismo año. Dicha red se adelantó a Segwit, Taproot y los activos emitidos. Contaba con un esquema de pegs de dos fases que, a la larga, demostró ser innecesariamente complejo y propenso a los errores. Fue rediseñada antes de su implementación en Liquid. Sus funcionalidades principales eran las transacciones confidenciales (Confidential Transactions)—que luego se ampliaron a fin de ser compatibles con los activos emitidos (Issued Assets)—, un esquema incipiente para segregar testigos que se simplificó y transfirió a Bitcoin como Segwit y un conjunto de códigos de operación adicionales para habilitar los convenios. Ese último elemento es el objeto de este artículo.

Cuando presentamos el conjunto original de códigos de operación de Elements y Liquid, Bitcoin Script dependía de un trabajo de programación manual de baja complejidad y, además, era difícil de analizar formalmente. Nuestras extensiones incrementaron la expresividad del idioma pero no solucionaron esas limitaciones subyacentes. Los usuarios lograron desarrollar algunas aplicaciones avanzadas, como los préstamos en Liquid, pero dichos avances dicen más de la persistencia del ingenio humano que de la facilidad de uso de nuestra plataforma de scripting. Otras aplicaciones, tales como Bitmatrix, no pudieron ser implementadas hasta que aparecieron las mejoras del sistema de scripts que se describen en este artículo. En el ínterin, independientemente de Elements, el equipo de investigación de Blockstream desarrolló Miniscript, una nueva manera de modelar Bitcoin Script que resolvería todos esos problemas de análisis.

En particular, con Miniscript es posible: enumerar automáticamente todas las claves de un script y determinar qué conjuntos se necesitan para producir una transacción; determinar un límite superior para el tamaño de dicha transacción antes de recopilar las firmas; responder preguntas semánticas sobre las condiciones que deben cumplirse para gastar monedas; y calcular la encriptación exacta de scripts y testigos. Miniscript complementa las transacciones parcialmente firmadas de Bitcoin (PSBT, por sus siglas en inglés), que también fueron ideadas por Andrew Chow, del equipo de investigación de Blockstream. Además, Pieter Wuille aportó parte del concepto y desarrollo original, que suministra un protocolo para reunir los datos necesarios para producir una transacción. Ese protocolo permite que las carteras firmen transacciones aunque no comprendan precisamente qué script satisfacen, o antes de que ello suceda: la cartera solo debe verificar que la transacción tenga sentido y producir una firma ECDSA o Schnorr, y las herramientas de Miniscript se encargan del resto.

No obstante, a pesar de contar con Miniscript, todavía no encontramos un camino directo que nos conduzca a convenios «fáciles» en Elements. Los tipos de convenios que construimos durante 2016 eran difíciles de usar en forma genérica. Además, en la práctica, eran casi siempre de gran tamaño, ya que estaban condicionados por el límite de 201 códigos de operación heredado de Bitcoin. Por ende, si bien Bitcoin Script se volvió más poderoso gracias a Miniscript, PSBT y Tapscript (diseñado con Miniscript en mente), no fue posible integrar las extensiones de Script de Elements.

Un antes y un después: nuevas abstracciones

En febrero de 2021, dos investigadores de Blockstream, Andrew Poelstra y Sanket Kanjalkar, por fin encontraron la manera de combinar los convenios de Elements con Miniscript. Básicamente, la idea es construir scripts compatibles con los convenios dentro de «la máquina», una plantilla de Script de tamaño fijo que emplea el código de operación CHECKSIGFROMSTACK (CSFS) para extraer los datos de todas las transacciones y depositarlos en la pila en ubicaciones fijas donde el «código real» puede acceder a ellos de manera eficiente. Esto nos permite amortizar los grandes costos de los convenios de tipo CSFS a través de numerosas condiciones en una transacción de gasto.

En rigor, más adelante dejamos atrás ese enfoque inicial, pero gracias a ese paso trascendimos la limitación conceptual de cómo construir convenios genéricos en la práctica y pudimos ver las limitaciones reales de Elements Script:

  • Aritmética descendente (big-endian) de 31 bits heredada de Bitcoin, difícil de conciliar con los montos en formato ascendente (little-endian) de 64 bits empleados en las transacciones.
  • Falta de códigos de operación de multiplicación y división, lo cual complica aún más las cosas. Elements agregó desplazamientos de bits (bitshifts) y otras operaciones aritméticas pero no los elementos básicos necesarios para el cálculo de comisiones, entre otros ejemplos.
  • En lo que respecta al cálculo de comisiones, los convenios de tipo CSFS no tienen acceso al peso de la transacción y, por ende, no pueden calcular la tasa correspondiente.
  • Imposibilidad de ejecutar un hash de más de 520 bytes de datos, causada por el límite de tamaño de los elementos de la pila de Bitcoin (520 bytes) y su inflexible código de operación SHA256. Esto se combina con la necesidad de CSFS de desmenuzar grandes cantidades de datos de transacciones de manera simultánea e impone límites al tamaño total de las transacciones, lo cual dificulta mucho el accionar de los creadores de las carteras.
  • Imposibilidad de ejecutar scripts con más de 201 códigos de operación, a pesar de que se necesitan decenas de ellos para la «máquina» de CSFS y otros tantos para convertir números enteros de 64 bits en enteros de 32 bits y viceversa.
  • Imposibilidad de efectuar operaciones aritméticas de curva elíptica, excepto en el caso de la validación de firmas, que puede ser manipulada para diversos usos pero no sirve para verificar que las salidas de Taproot estén bien formadas. Si hubiésemos retenido esa limitación en Tapscript, hubiese disminuido la funcionalidad y, al mismo tiempo, incrementado la complejidad de nuestros scripts.

Una vez que identificamos dichos problemas, fue sencillo solucionarlos:

  • Agregamos códigos de operación nuevos para manipular valores de 64 bits, incluida la multiplicación, así como también códigos de operación para convertirlos a 32 bits y utilizarlos con los códigos de operación habituales de Bitcoin.
  • Sumamos códigos de operación de «introspección directa» para acceder a los datos de las transacciones, con lo cual eliminamos la necesidad de recurrir a «la máquina» y disminuimos la cantidad de códigos de operación. También sumamos el código de operación TXWEIGHT para acceder al peso de la transacción y habilitar el cálculo de comisiones.
  • Sumamos códigos de operación de tipo «flujo de hash» (streaming hash) que permiten ingresar datos en un motor hash sin necesidad de combinarlos todos en un solo elemento de la pila. Con esta elegante solución, se evita el límite de tamaño de los elementos la pila (520 bytes) sin que el intérprete del script se vea obligado a utilizar más recursos.
  • Agregamos el código de operación ECMULSCALARVERIFY para gestionar las salidas de Taproot, los compromisos de «pago al contrato» (pay to contract) y otras operaciones de criptografía de curva elíptica.
  • Decidimos seguir los pasos de Bitcoin, que eliminó el límite de 201 códigos de operación, aunque quizás podríamos haberlo afrontado gracias a las mejoras mencionadas.

Una vez listas estas soluciones, nos encontramos con un conjunto no muy grande de códigos de operación nuevos que encajan fácilmente en el modelo Bitcoin Script —es decir, no introducimos acceso a discos, uso irrestricto de los recursos o flujos de control complejos, como los bucles (looping)— y que resuelven grandes problemas, de tal modo que posibilitan convenios de uso intensivo en Elements.

Aquí encontrará una lista completa de los códigos de operación nuevos.

Ahora que nos sobrepusimos a dicho obstáculo, estamos listos para afrontar algunas problemáticas más profundas de los convenios, que no son exclusivas de Elements.

Un ejemplo concreto de las nuevas posibilidades que encierran estos códigos de operación reside en el esquema de bóvedas: con MAX_WITHDRAW podemos restringir el máximo de fondos que pueden extraerse de una vez dentro de un lapso de sesenta bloques.

Para crear dicho convenio, se deben cumplir las siguientes condiciones (expuestas aquí en notación Miniscript, que abordaremos en detalle en otra publicación).

  • Extracción parcial (partial_withdraw): Si hay más de MAX_WITHDRAW sats en el convenio, el saldo restante del convenio debería ser por lo menos total_value - MAX_WITHDRAW.
  • "num64_gt(curr_inp_v,MAX_WITHDRAW)": Las entradas en sats superan MAX_SATS
  • "asset_eq(curr_inp_asset,out_asset(0))": Los activos de entrada y el activo de salida 0 son iguales.
  • "num64_geq(out_v(0),sub64(curr_inp_v,MAX_WITHDRAW))": El valor de salida debería ser por lo menos total_value - MAX_WITHDRAW
  • "spk_eq(curr_inp_spk,out_spk(0))": Las monedas restantes se envían al convenio.
  • Extracción total (full_withdraw): Si el convenio tiene menos de MAX_WITHDRAW sats, no se aplican restricciones a las salidas.
  • num64_leq(curr_inp_v,MAX_WITHDRAW)
  • Control de llaves (keys): Deberá esperar a que transcurran 60 bloques antes de volver a gastar y se le requerirá una llave (K) para el próximo gasto.
  • pk(K): Requiere la firma con la llave K.
  • older(60): Tiempo de espera de 60 bloques antes de la siguiente extracción.
  • curr_idx_eq(0): El índice de gasto actual debe ser igual a cero. Esta condición previene el denominado «problema del gasto a medias», que ocurre cuando se gastan dos UTXO del convenio a la vez y uno de ellos se «escapa» del convenio.
  • Por último, combinamos todas las condiciones anteriores: and(keys,or(full_withdraw,partial_withdraw))

Es posible ampliarlas con claves de extracción de emergencia, extracciones en múltiples fases, registro en listas blancas y custodias multi-sig.

Problemáticas pendientes y trabajo a futuro

Al expandir Miniscript con recursos para convenios y proporcionar códigos de operación para hacerlo de manera eficiente, creamos un marco donde los usuarios pueden construir scripts de convenios con facilidad, comprender su comportamiento y crear transacciones completas que ejecuten esos convenios en un contrato. Pero, al hacerlo, socavamos la estructura recursiva de Miniscript y, en consecuencia, alteramos algunas formas de análisis.

Por ejemplo, es posible «componer» dos Miniscripts mediante la creación de un nodo and_b cuyos dos hijos sean los dos scripts originales. (En términos de Script, lo que estamos haciendo es concatenar los scripts originales y agregar un código de operación BOOLAND al final.) Como usuarios, si estamos convencidos de que podemos satisfacer cada uno de los scripts originales de manera independiente, se nos garantiza que podremos satisfacer el script combinado.

Del mismo modo, si deseamos determinar la posibilidad de satisfacer un script complejo, podemos avanzar por el script de manera recursiva: cada vez que nos topemos con un nodo and tendremos que satisfacer ambos sub-scripts, pero si nos encontramos con un nodo or solo tendremos que satisfacer uno de ellos.

En los convenios, esta forma de composición ya no es válida. Si bien todavía es posible combinar dos scripts mediante and_b y obtener un script sintácticamente válido, el resultado podría ser imposible de satisfacer aunque se satisfagan ambos sub-scripts. Por ejemplo, podría combinarse un script que exige que la primera salida de la transacción queme el activo A con un script que exige que la primera salida de la transacción queme el activo B. Claramente, no es posible satisfacer los dos a la vez.

En general, dado que los convenios imponen condiciones globales a la transacción en construcción, ciertos fragmentos particulares del script pueden tener efectos mutuos no localizados. Alternativamente, cabe señalar que en Bitcoin Miniscript podemos entender nuestros scripts como funciones monótonas de las condiciones de gastos. Un script representa una regla para decidir qué conjuntos de condiciones de gasto (firmas, preimágenes de hash, etcétera) son válidos para gastar fondos y cuáles son inválidos. El carácter monótono de dichas reglas implica que si un conjunto dado de condiciones es válido, cualquier conjunto mayor, que lo comprenda, también lo es.

Con los convenios, ese modelo de funciones monótonas ya no resulta aplicable, de modo que se pierden las poderosas herramientas de análisis general que proporciona Miniscript. Por el contrario, los programas de Elements Miniscript compatibles con convenios deben ser analizados de manera ad-hoc, es decir, haciendo y respondiendo preguntas específicas.

Simplicity, una alternativa a gran escala de Script que estamos desarrollando en paralelo, también posee esta limitación. Para que dicho análisis ad-hoc resulte viable, el intérprete de Simplicity posee una implementación de referencia en el asistente de prueba de teoremas Coq, de modo que, si bien es necesario que un ser humano formule las preguntas correctas, al menos las respuestas serán totalmente certeras y podrán ser verificadas de manera automatizada. En el caso de Elements Miniscript, los programas dependen en todo sentido de una revisión del código efectuada por seres humanos. Esperamos que nuestro diseño redunde en programas poderosos y, a la vez, sencillos y manifiestamente correctos.

De aquí en adelante, seguiremos expandiendo la funcionalidad de Elements Miniscript, elaboraremos bibliotecas auxiliares de carteras y herramientas que lo empleen para producir convenios e interactuar con ellos, y seguiremos avanzando con Simplicity, que será todavía más expresivo que el recientemente renovado Elements Script.

¡A seguir hackeando!

If you have specific preferences, please, mark the topic(s) you would like to read: