Bitcoin permite que los usuarios elijan las reglas que controlan cómo se pueden gastar sus fondos; luego la red las implementa automáticamente. Esta facilidad programática permite que los usuarios fijen sus propias políticas, desde las más sencillas, como las multifirmas, hasta las más complejas, como el intercambio independiente de la confianza. No obstante, existen algunas políticas que actualmente no hallan una expresión obvia en el sistema Bitcoin. En este artículo, abordamos un ejemplo de este tipo de políticas, los convenios, y describimos cómo implementarlos con una pequeña extensión del lenguaje de script de Bitcoin que ya existe en Elements.
Un convenio financiero es un acuerdo que restringe cómo se pueden utilizar determinados fondos (por lo general, los préstamos). Aplicando el lenguaje de scriping de Bitcoin, es posible habilitar esquemas de autorización que determinen quiénes tienen permitido crear transacciones que gasten ciertos fondos en particular. Sin embargo, a todas las partes capaces de cumplir con los requisitos de autorización se les permitirá crear cualquier transacción que deseen con dichos fondos. Gracias a los convenios, podemos crear scripts que no solo impongan esquemas de autorización, sino que además restrinjan los tipos de transacciones en las cuales se pueden gastar los fondos. Por ejemplo, podemos emplear la bóveda Möser-Eyal-Sirer para bloquear temporalmente ciertas transacciones y para detectar accesos malintencionados a los fondos propios.
Al igual que Bitcoin, el lenguaje de script de Elements Alpha no cuenta con acceso directo a los datos de la transacción. Solo es posible acceder a los datos de la transacción de manera indirecta, mediante las operaciones OP_CHECKSIG y OP_CHECKSIGVERIFY. Dichas operaciones toman como datos de entrada una clave pública de curva elíptica y una firma digital, sumadas a un tipo de función hash de firma. Primero computan un hash doble SHA-256 de los datos de la transacción, modificados conforme al tipo de hash de firma especificado, y luego ejecutan una verificación de la firma digital de los datos del mensaje con doble hash con la firma digital y la clave pública. Si la operación se ejecuta con éxito, OP_CHECKSIG arroja un valor de 1 en la pila; si no, arroja 0.
Las nuevas operaciones OP_CHECKSIGFROMSTACK y OP_CHECKSIGFROMSTACKVERIFY de Elements Alpha requieren tres componentes: una clave pública de curva elíptica, un mensaje y una firma digital. Estas operaciones efectúan un solo hash SHA-256 del mensaje y, a continuación, una verificación de la firma digital de los datos del mensaje con hash con la firma digital y la clave pública. Si la operación se ejecuta con éxito, OP_CHECKSIGFROMSTACK arroja un valor de 1 en la pila; si no, arroja 0.
El secreto para crear un convenio en Elements Alpha es aprovechar el hecho de que una operación OP_CHECKSIG exitosa implica que la clave pública y la firma digital, en conjunto, conforman un compromiso con los datos de la transacción. Si la misma clave pública y la misma firma digital se emplean en una operación OP_CHECKSIGFROMSTACK exitosa, el mensaje ingresado a OP_CHECKSIGFROMSTACK debe ser idéntico a los datos de la transacción. Mediante el uso conjunto de OP_CHECKSIG y OP_CHECKSIGFROMSTACK, creamos una garantía criptográfica de que el mensaje transmitido a OP_CHECKSIGFROMSTACK consiste en los datos firmados de la transacción.
Construcción del convenio
Considere el siguiente script:
- OP_OVER OP_SHA256 <pubKey>
- 2 OP_PICK 1 OP_CAT OP_OVER
- OP_CHECKSIGVERIFY
- OP_CHECKSIGFROMSTACKVERIFY
Supongamos que, en principio, hay dos elementos en la pila.
</code> {:.ui.segment} Después del paso 1, colocamos un hash al `` una sola vez con SHA-256 e ingresamos una clave pública a la pila. <sha256(sigTransactionData)></code> {:.ui.segment} Después del paso 2, copiamos la firma y la clave pública al frente de la pila. La firma tiene adjunto el byte `0x01`. Este es el código del tipo de hash `SIGHASH_ALL`, que especifica que los datos firmados de la transacción contienen todas las entradas y salidas. <signature;SIGHASH_ALL> <sha256(sigTransactionData)></code> {:.ui.segment}
El comando `OP_CHECKSIGVERIFY` del paso 3 corrobora que se haya ingresado una firma válida y demuestra que la transacción fue autorizada con la clave privada. <sha256(sigTransactionData)> </code> {:.ui.segment} El comando `OP_CHECKSIGFROMSTACKVERIFY` del paso 4 emplea la misma clave pública y la misma firma. Esta operación efectúa otro SHA-256 y realiza una validación de firmas digitales del `` con doble hash. Dado que estamos empleando exactamente la misma clave pública y la misma firma que en la operación `OP_CHECKSIGVERIFY` anterior, este comando `OP_CHECKSIGFROMSTACKVERIFY` solo puede ejecutarse con éxito si `` es idéntico al mensaje que se verificó durante la operación `OP_CHECKSIGVERIFY` del paso 3. </code> {:.ui.segment} Llegado este punto del script, ya estamos seguros de que `` es una copia exacta de los datos firmados de la transacción. Y, como tenemos acceso total a los datos de la transacción, podemos agregar más operaciones de script para implementar un convenio. Podemos restringir el número de salidas. Podemos restringir el valor de las salidas. Podemos restringir los comandos de las salidas. Esta técnica para crear convenios tiene algunas limitaciones. En el script de Elements Alpha, todos los elementos de la pila deben atenerse a un tamaño máximo de 520 bytes. Esto quiere decir que solo podemos crear convenios para `` que respeten dicho límite. Aun así, respetando dicho límite podemos crear convenios interesantes. ## Convenios recursivos. En esta sección, construiremos un convenio recursivo trivial. Mediante este convenio, vamos a restringir las transacciones para que solo se puedan efectuar aquellas donde exista una sola salida cuyo comando de salida sea idéntico al de entrada. Este convenio es un juguete, pero ilustra la receta básica para construir convenios recursivos. Más adelante, vamos a construir convenios más interesantes. En lugar de ingresar todos los datos de la transacción firmados a la pila y analizarlos, este script ensambla los datos de la transacción a partir de fragmentos. Luego, el script puede imponer condiciones a cada fragmento para que implemente el convenio. Es importante determinar que todos los fragmentos de datos sean de un tamaño fijo conocido porque, de lo contrario, el script podría ser atacado si se ingresan fragmentos extra largos o extra cortos que confundan al script, que creería estar analizando un campo de los datos de la transacción cuando en realidad está analizando otro distinto. Copiamos a continuación el scriptPubKey de nuestro convenio trivial. 1. `<0x0100000001>` 1. `OP_SWAP` `OP_SIZE` `36` `OP_NUMEQUALVERIFY` `OP_CAT` 1. `<0x00>` `OP_CAT` 1. `OP_SWAP` `OP_SIZE` `32` `OP_NUMEQUALVERIFY` `OP_CAT` 1. `<0x00005f>` `OP_CAT` 1. `2` `OP_PICK` `OP_SIZE` `95` `OP_NUMEQUALVERIFY` `OP_CAT` 1. `<0xffffffff0100>` `OP_CAT` 1. `OP_SWAP` `OP_SIZE` `32` `OP_NUMEQUALVERIFY` `OP_CAT` 1. `<0x0000>` `OP_HASH256` `OP_CAT` 1. `<0x17a914>` `OP_CAT` 1. `OP_SWAP` `OP_HASH160` `OP_CAT` 1. `<0x870000000001000000>` `OP_CAT` 1. `OP_SHA256` 1. `1` `OP_DUP` `OP_CAT` `OP_DUP` `OP_CAT` `OP_DUP` `OP_CAT` `OP_DUP` `OP_CAT` `OP_DUP` `OP_CAT` `OP_DUP` `OP_CAT` 1. `OP_DUP` 1. `2` `OP_ROLL` `3` `OP_PICK` 1. `OP_CHECKSIGFROMSTACKVERIFY` 1. `1` `OP_CAT` `OP_SWAP` 1. `OP_CHECKSIG` El scriptSig correspondiente para gastarlo es 1. `` 1. `