WEB3DEV Español

Cover image for El Primer Nivel Alto de Lenguaje del Contrato Inteligente en BTC
Hector
Hector

Posted on

El Primer Nivel Alto de Lenguaje del Contrato Inteligente en BTC

Ejecuta un contrato inteligente sCrypt en BTC

Este artículo es una traducción de sCrypt, hecha por Héctor Botero. Puedes encontrar el artículo original aquí.
Sería genial escucharte en nuestro Discord, puedes contarnos tus ideas, comentarios, sugerencias y dejarnos saber lo que necesitas.
Si prefieres puedes escribirnos a @web3dev_eshttps://twitter.com/web3dev_es en Twitter.

Estamos emocionados de anunciar el primer contrato inteligente escrito en un idioma de programación de alto nivel en la red de Bitcoin Core (BTC).

El contrato involucra dos transacciones, los cuales pueden ser vistos usando un explorador de bloques:

  1. Deploy txid: f7b0d5c2a1b70a3ce13afe06f867a9f3c60fd73c9756bb4f37a343e9a8f9d4c1
  2. Spend txid: cf48d9d242bd19a70042609d1602f39ca4191830b656e6d1d9660ba9fa529a8e

Es crucial entender que toda la lógica del contrato inteligente es reforzado en la Capa 1 en la forma del Bitcoin Script, el cual es integrado en los datos del testigo.

Proveemos una guía paso a paso en cómo desplegar un contrato inteligente de sCrypt en BTC, desde compilar hasta desplegarlo y, finalmente, llamarlo. También dilucidar las limitaciones actuales en la cadena BTC y cómo obstaculizan el desarrollo.

¿Qué es sCrypt?

sCrypt es un framework del tipo de Typescript para escribir contratos inteligentes en Bitcoin. Permite a los desarrolladores a programar contratos inteligentes directamente en TypeScript, uno de los lenguajes de programación de alto nivel más populares, conocido por millones de desarrolladores en el mundo. Un contrato inteligente de Scrypt, compila a Script, el lenguaje de script de programación nativo de Bitcoin, el cual es incluído dentro de la transacción para hacer cumplir una condición de gasto arbitraria. Provee una abstracción más amigable, para los desarrolladores, sobre Script.

sCript es original y primariamente, diseñado por Bitcoin SV, pero el script que compila puede también ser aplicado a otros Bitcoins forks y otras cadenas derivadas como: BTC, BCH, LiteCoin o DogeCoin, ya que usa un Script de Bitcoin compatible.

A continuación te mostraremos cómo el contrato inteligente más popular en Bitcoin hoy, llamado Pay to Public Key Hash, es codeado en sCrypt.

Image description

P2WSH

Pay to Witness Script Hash (P2WSH) es un tipo de script de bloqueo en BTC, introducido en la actualización de los Segregated Witness (SegWit). Es similar al Pay to Script Hash (P2SH), excepto que usa SegWit.

Image description

Aquí hay un vistazo simplificado de cómo P2WSH funciona:

  1. Creación: Un P2WSH UTXO (Unspent Transaction Output) es creado enviando bitcoins al hash del witness o del redeem script, como en P2SH. Este redeem script es el contrato inteligente compilado del sCrypt en nuestro caso.
  2. Gasto: Para gastar esos bitcoins, el gastador presenta el redeem script original y cualquier witness requerido (es decir, el método de argumentos públicos del contrato inteligente de sCrypt). La red del Bitcoin verifica que el redeem script proveído, coincida con el hash del output anterior y que el script se ejecute exitosamente con el witness dado.

Puzles de hash para múltiples grupos

En el puzle del hash del contrato, el gastador tiene que proveer una preimagen x que hashea a un valor predefinido y para desbloquear al UTXO. Puede ser extendido a múltiples grupos para que múltiples preimagenes tengan que ser proveídas como y1 = H(x1), y2 = H(x2), …, yN = H(xN).

Esto puede ser codeado fácilmente en sCrypt:

import { assert, FixedArray, ByteString, method, prop, Sha256, SmartContract, sha256 } from 'scrypt-ts'
type HashArray = FixedArray<Sha256, typeof MultiPartyHashPuzzle.N>
type PreimageArray = FixedArray<ByteString, typeof MultiPartyHashPuzzle.N>
export class MultiPartyHashPuzzle extends SmartContract {
static readonly N = 3
@prop()
readonly hashes: HashArray
constructor(hashes: HashArray) {
super(...arguments)
this.hashes = hashes
}

@method()
public unlock(preimages: PreimageArray) {
for (let i = 0; i < MultiPartyHashPuzzle.N; i++) {
assert(sha256(preimages[i]) == this.hashes[i], 'hash mismatch')
}
}
}
Enter fullscreen mode Exit fullscreen mode

Desplegar y Llamar

Para propósitos de demostración, desplegaremos un contrato de puzles de hash para múltiples grupos.

Primero compilamos el contrato y creamos una instancia, es decir, inicializarlo con valores generados aleatoriamente:

await MultiPartyHashPuzzle.compile()
const _hashes = []
for (let i = 0; i < MultiPartyHashPuzzle.N; i++) {
const preimage = generateRandomHex(32)
_hashes.push(sha256(preimage))
}

hashes = _hashes as FixedArray<Sha256, typeof MultiPartyHashPuzzle.N>
instance = new MultiPartyHashPuzzle(hashes)
Enter fullscreen mode Exit fullscreen mode

Ahora podemos extraer nuestro redeem script:

console.log(instance.lockingScript.toHex())
Enter fullscreen mode Exit fullscreen mode

Esto imprimirá el script serializado:

0000018257615179547a75537a537a537a0079537a75527a527a7575615279008763537952795279615179517993517a75517a75619c77777777675279518763537952795279949c7777777767006868
Enter fullscreen mode Exit fullscreen mode

Ahora, vamos a transmitir la transacción P2WSH conteniendo el hash de este script. Para esto, usaremos la biblioteca Python bitcoin-utils:

from bitcoinutils.setup import setup
from bitcoinutils.utils import to_satoshis
from bitcoinutils.transactions import Transaction, TxInput, TxOutput
from bitcoinutils.keys import PrivateKey, P2wshAddress, P2wpkhAddress
from bitcoinutils.script import Script
def main():
# always remember to setup the network
setup('mainnet')
priv0 = PrivateKey("")
pub = priv0.get_public_key()
fromAddress = pub.get_segwit_address()

# P2SH Script:

p2sh_redeem_script = Script.from_raw('000000201e3457f730d001d0fbe60830bded73df7166afd38ab3ac273e385003094371c0205657aa4c420a961153d539f4501457995fad8d27e17a5ec7ffda449e8b8aab1e20e60d1fb5e71b6327214e2e9503027c3ba44331562a8e1d193a1e759bcc27fc4161527952795279587a587a587a757575557a557a557a757575616155007600a26976539f6961946b6c766b796c75a853795379537953007600a26976539f6994618c6b6c766b796c756b7575756c87696155517600a26976539f6961946b6c766b796c75a853795379537953517600a26976539f6994618c6b6c766b796c756b7575756c87696155527600a26976539f6961946b6c766b796c75a853795379537953527600a26976539f6994618c6b6c766b796c756b7575756c876951777777777777')

toAddress = P2wshAddress.from_script(p2sh_redeem_script)
# set values
txid = 'a6d5e6f5f36c5587653df4841b1ec8e726018b15cadeeb542b2da780b38e9a21'
vout = 0
amount = 0.00296275
fee = 0.00010000

# create transaction input from tx id of UTXO
txin = TxInput(txid, vout)
redeem_script1 = Script(
['OP_DUP', 'OP_HASH160', priv0.get_public_key().to_hash160(), 'OP_EQUALVERIFY', 'OP_CHECKSIG'])

# create transaction output
txOut = TxOutput(to_satoshis(amount - fee), toAddress.to_script_pub_key())

# create transaction
tx = Transaction([txin], [txOut], has_segwit=True)
print("\nRaw transaction:\n" + tx.serialize())
sig1 = priv0.sign_segwit_input(tx, 0, redeem_script1, to_satoshis(amount))
tx.witnesses.append(Script([sig1, pub.to_hex()]))

# print raw signed transaction ready to be broadcasted
print("\nRaw signed transaction:\n" + tx.serialize())
print("\nTxId:", tx.get_txid())
if __name__ == "__main__":
main()
Enter fullscreen mode Exit fullscreen mode

Esto imprimirá la transacción P2WSH firmada y serializada, se verá algo así:

02000000000101219a8eb380a72d2b54ebdeca158b0126e7c81e1b84f43d6587556cf3f5e6d5a60000000000ffffffff01435e0400000000002200203463b81678556aea41c76646be324ee16b676331d652dd650c1add2c83e064aa02473044022011f96e5e79905a7cb45a3952dad27df25bb7aa5de0a9d36bd37dcc5d3b0f70ec022055f54335ef75d1a32433c19d47356efc40705c950e80a55924516fa5f5094957012102e53bd683720e9e759ea9b958d3faaeaaeda3345db42ffe77be3db212535f465600000000
Enter fullscreen mode Exit fullscreen mode

Podemos transmitirla simplemente copiándola y pegándola en este sitio, y haremos click en “Broadcast transaction”.

Image description

Una vez transmitido, podemos construir la transacción redimible que gastará nuestro output P2WSH. De nuevo, usaremos un script de Python.

def main():
# always remember to setup the network
setup('mainnet')

p2wsh_witness_script = Script.from_raw('000000201e3457f730d001d0fbe60830bded73df7166afd38ab3ac273e385003094371c0205657aa4c420a961153d539f4501457995fad8d27e17a5ec7ffda449e8b8aab1e20e60d1fb5e71b6327214e2e9503027c3ba44331562a8e1d193a1e759bcc27fc4161527952795279587a587a587a757575557a557a557a757575616155007600a26976539f6961946b6c766b796c75a853795379537953007600a26976539f6994618c6b6c766b796c756b7575756c87696155517600a26976539f6961946b6c766b796c75a853795379537953517600a26976539f6994618c6b6c766b796c756b7575756c87696155527600a26976539f6961946b6c766b796c75a853795379537953527600a26976539f6994618c6b6c766b796c756b7575756c876951777777777777')

fromAddress = P2wshAddress.from_script(p2wsh_witness_script)
toAddress = P2wpkhAddress.from_address("bc1q99dsng0pq93r6t97llcdy3s7xz96qer03f55wz")

# set values
txid = 'f7b0d5c2a1b70a3ce13afe06f867a9f3c60fd73c9756bb4f37a343e9a8f9d4c1'
vout = 0
amount = 0.00286275
fee = 0.00010000

# create transaction input from tx id of UTXO
txin = TxInput(txid, vout)
txOut1 = TxOutput(to_satoshis(amount - fee), toAddress.to_script_pub_key())
tx = Transaction([txin], [txOut1], has_segwit=True)
tx.witnesses.append(Script([

'abc57d70dc5e56ac73e2970077adaf94accfb36dac2b40d34f807cdedad0807b',

'fe4f237f4e51053fa4cebc55ee15ffd9dd654c2e3b928d4a6243d1f1bcb57ca7',

'5776ddb41c760c1965401fc4521531c3db68cf1023ce5a294a201ccfc887bc85',

p2wsh_witness_script.to_hex()]))

# print raw signed transaction ready to be broadcasted
print("\nRaw signed transaction:\n" + tx.serialize())
print("\nTxId:", tx.get_txid())
if __name__ == "__main__":
main()
Enter fullscreen mode Exit fullscreen mode

La parte más significativa del script de arriba es cuando configuramos los datos del witness, el cual contiene las preimágenes correctas en la línea 25-57.

Luego de ejecutar el script, tendremos la transacción pura, como esta:

02000000000101c1d4f9a8e943a3374fbb56973cd70fc6f3a967f806fe3ae13c0ab7a1c2d5b0f70000000000ffffffff013337040000000000160014295b09a1e101623d2cbefff0d2461e308ba0646f0420abc57d70dc5e56ac73e2970077adaf94accfb36dac2b40d34f807cdedad0807b20fe4f237f4e51053fa4cebc55ee15ffd9dd654c2e3b928d4a6243d1f1bcb57ca7205776ddb41c760c1965401fc4521531c3db68cf1023ce5a294a201ccfc887bc85fd2901000000201e3457f730d001d0fbe60830bded73df7166afd38ab3ac273e385003094371c0205657aa4c420a961153d539f4501457995fad8d27e17a5ec7ffda449e8b8aab1e20e60d1fb5e71b6327214e2e9503027c3ba44331562a8e1d193a1e759bcc27fc4161527952795279587a587a587a757575557a557a557a757575616155007600a26976539f6961946b6c766b796c75a853795379537953007600a26976539f6994618c6b6c766b796c756b7575756c87696155517600a26976539f6961946b6c766b796c75a853795379537953517600a26976539f6994618c6b6c766b796c756b7575756c87696155527600a26976539f6961946b6c766b796c75a853795379537953527600a26976539f6994618c6b6c766b796c756b7575756c87695177777777777700000000
Enter fullscreen mode Exit fullscreen mode

De nuevo, podemos simplemente transmitirlo usando el mismo link de antes.

¡Voila! Hemos desplegado e invocado, exitosamente, un contrato inteligente de sCrypt en el blockchain BTC.

Image description

Las limitaciones severas del BTC

BTC sufre de grandes y severos inconvenientes para el desarrollo de contratos inteligentes complejos, principalmente, por sus muchos op codes desactivados y las limitaciones artificiales impuestas en la transacción y el tamaño del script.

Image description

En contraste, BSV permite sacar todo el poderío de los contratos inteligentes del Bitcoin, recuperando todos los opcodes y removiendo todos los límites artificiales. Ha habido una explosión de contratos inteligentes desarrollados en Cambrian como: prueba de conocimiento cero, máquinas Turing, ZK-Rollup y Redes Neuronales Profundas.

Un ejemplo concreto es el mencionado anteriormente, los puzles de hash de grupos múltiples que pueden ser optimizados usando un opcode concatenado, OP_CAT, el cual es reactivado en BSV pero aún deben ser desactivados en BTC. Anteriormente, todos los hashes N, tienen que ser incluídos en el script bloqueado, inflando la transacción cuando N es largo. En cambio, podemos combinar todos los y en un solo y. Por ejemplo, cuando N es 3, permitimos que y = H(H(H(y1) || y2) || y3), el cual || es concatenado. El nuevo contrato optimizado es así:

import { assert, FixedArray, ByteString, method, prop, Sha256, SmartContract, sha256, toByteString } from 'scrypt-ts'
type PreimageArray = FixedArray<ByteString, typeof MultiPartyHashPuzzle.N>
export class MultiPartyHashPuzzleOpt extends SmartContract {
static readonly N = 10
@prop()
readonly combinedHash: Sha256
constructor(combinedHash: Sha256) {
super(...arguments)
this.combinedHash = combinedHash
}

@method()
public unlock(preimages: PreimageArray) {
let combinedHash: ByteString = toByteString('')
for (let i = 0; i < MultiPartyHashPuzzle.N; i++) {
combinedHash = sha256(combinedHash + preimages[i])
}

assert(combinedHash == this.combinedHash, 'hash mismatch')
}
}
Enter fullscreen mode Exit fullscreen mode

En la línea 20, la concatenación (+) es usada, el cual no trabaja con BTC.

Conclusión

Hemos demostrado como un contrato inteligente de sCrypt puede ejecutarse en BTC. Similarmente, puede ejecutar cualquier cadena compatible con Script como BCH, LiteCoin y DogeCoin. Dado a sus severas limitaciones, tiene una ejecución increíblemente limitada, a diferencia de BSV, el cual, es la única cadena sin límites y escalable que realiza el potencial entero de sCrypt.

El código entero está aquí.

Discussion (0)