WEB3DEV

Cover image for O primeiro Contrato Inteligente de linguagem de alto nível no BTC
Isabela Curado Nehme
Isabela Curado Nehme

Posted on

O primeiro Contrato Inteligente de linguagem de alto nível no BTC

Execute um contrato inteligente com sCrypt no BTC

12 de maio de 2023

Temos o prazer de anunciar o primeiro contrato inteligente bem sucedido escrito em uma linguagem de programação de alto nível na rede Bitcoin Core (BTC).

O contrato envolve duas transações, que podem ser visualizadas usando um explorador de blocos:

Implantar TXID: f7b0d5c2a1b70a3ce13afe06f867a9f3c60fd73c9756bb4f37a343e9a8f9d4c1

Gastar TXID: cf48d9d242bd19a70042609d1602f39ca4191830b656e6d1d9660ba9fa529a8e

É crucial entender que toda a lógica do contrato inteligente é aplicada na Camada 1 na forma de um script do Bitcoin, que é integrado aos dados da testemunha.

Fornecemos um guia passo a passo sobre como implantar um contrato inteligente com o sCrypt no BTC, desde a compilação até a implantação e finalmente a chamada. Também elucidaremos as limitações atuais da cadeia BTC e como elas impedem o desenvolvimento.

O que é o sCrypt?

O sCrypt é um framework de TypeScript para a escrita de contratos inteligentes no Bitcoin. Ele permite que os desenvolvedores programem contratos inteligente diretamente no TypeScript¹, uma das linguagens de programação de alto nível mais populares conhecida por milhões de desenvolvedores em todo o mundo. Um contrato inteligente em sCrypt compila para Script, a linguagem de script nativa do Bitcoin, que é então incluída em uma transação para impor uma condição de gasto arbitrária. Ele fornece uma abstração mais amigável ao desenvolvedor em relação ao Script.

O sCrypt é originalmente e principalmente projetado para o Bitcoin SV, mas o script que ele compila pode ser também aplicado nas bifurcações (forks) do Bitcoin e cadeias derivadas, como BTC, BCH, LiteCoin ou DogeCoin, desde que usem o Script compatível com o Bitcoin.

A seguir você encontra como é o contrato inteligente mais popular no Bitcoin hoje, isto é, o Pay to Public Key Hash, que está codificado em sCrypt.

P2WSH

O Pay to Witness Script Hash (P2WSH) é um tipo de script de bloqueio em BTC, introduzido na atualização Segregated Witness (SegWit). É semelhante a um Pay to Script Hash (P2SH), exceto por utilizar o SegWit.

Crédito P2SH: learnmeabitcoin

Aqui está uma visão geral simplificada de como o P2WSH funciona:

Criação: Um P2WSH UTXO (Unspent Transaction Output - Saída de transação não gasta) é criado através do envio de bitcoins ao hash de uma assinatura ou script de resgate, como no P2SH. Esse script de resgate é o contrato inteligente em sCrypt compilado, em nosso caso.
Gastos: Para gastar esses bitcoins, o gastador apresenta o script de resgate original e qualquer assinatura necessária (ou seja, os argumentos do método público de contratos inteligentes em sCrypt). A rede Bitcoin verifica se o script de resgate fornecido corresponde ao hash na saída anterior e se o script é executado com sucesso a assinatura fornecida.

Quebra-cabeças de hash multipartidários (Multi-party hash puzzles)

Em um contrato de quebra-cabeças de hash, o gastador deve fornecer uma pré-imagem x que faz um hash com um valor predefinido y para desbloquear um UTXO. Pode ser estendido a múltiplas partes para que várias pré-imagens tenham que ser fornecidas de modo que y1 = H(x1), y2 = H(x2), …, yN = H(xN).

Pode ser facilmente codificado no 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

Implantar e chamar

Para fins de demonstração, implantaremos um contrato quebra-cabeças de hash multipartidário.

Primeiro, compilamos o contrato e criamos uma instância, ou seja, inicializar-lo com valores gerados 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

Agora, podemos extrair nosso script de resgate:

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

Isso irá imprimir o script serializado:

0000018257615179547a75537a537a537a0079537a75527a527a7575615279008763537952795279615179517993517a75517a75619c77777777675279518763537952795279949c7777777767006868
Enter fullscreen mode Exit fullscreen mode

Agora, vamos transmitir uma transação P2WSH contendo um hash deste script. Para isso, usamos a biblioteca do 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():
    # lembre-se sempre de configurar a rede
    setup('mainnet')

    priv0 = PrivateKey("")

    pub = priv0.get_public_key()
    fromAddress = pub.get_segwit_address()

    # Script P2SH:
    p2sh_redeem_script = Script.from_raw('000000201e3457f730d001d0fbe60830bded73df7166afd38ab3ac273e385003094371c0205657aa4c420a961153d539f4501457995fad8d27e17a5ec7ffda449e8b8aab1e20e60d1fb5e71b6327214e2e9503027c3ba44331562a8e1d193a1e759bcc27fc4161527952795279587a587a587a757575557a557a557a757575616155007600a26976539f6961946b6c766b796c75a853795379537953007600a26976539f6994618c6b6c766b796c756b7575756c87696155517600a26976539f6961946b6c766b796c75a853795379537953517600a26976539f6994618c6b6c766b796c756b7575756c87696155527600a26976539f6961946b6c766b796c75a853795379537953527600a26976539f6994618c6b6c766b796c756b7575756c876951777777777777')


    toAddress = P2wshAddress.from_script(p2sh_redeem_script)

    # define valores
    txid = 'a6d5e6f5f36c5587653df4841b1ec8e726018b15cadeeb542b2da780b38e9a21'
    vout = 0
    amount = 0.00296275
    fee = 0.00010000

    # cria a entrada da transação do id tx do UTXO
    txin = TxInput(txid, vout)
    redeem_script1 = Script(
        ['OP_DUP', 'OP_HASH160', priv0.get_public_key().to_hash160(), 'OP_EQUALVERIFY', 'OP_CHECKSIG'])

    # cria a saída da transação
    txOut = TxOutput(to_satoshis(amount - fee), toAddress.to_script_pub_key())

    # cria a transação
    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()]))

    # imprime a transação assinada bruta pronta para ser transmitida
    print("\nRaw signed transaction:\n" + tx.serialize())
    print("\nTxId:", tx.get_txid())


if __name__ == "__main__":
    main()
Enter fullscreen mode Exit fullscreen mode

Isso irá imprimir a transação P2WSH serializada e assinada, algo como o que se segue:

Enter fullscreen mode Exit fullscreen mode

Podemos transmiti-la apenas copiando e colando a mesma neste site e clicando em “Broadcast transaction” (transmitir transação).

Uma vez transmitida, podemos construir a transação de resgate que gastará nossa saída P2WSH. Novamente, usaremos um script em Python:

def main():
    # lembre-se sempre de configurar a rede
    setup('mainnet')

    p2wsh_witness_script = Script.from_raw('000000201e3457f730d001d0fbe60830bded73df7166afd38ab3ac273e385003094371c0205657aa4c420a961153d539f4501457995fad8d27e17a5ec7ffda449e8b8aab1e20e60d1fb5e71b6327214e2e9503027c3ba44331562a8e1d193a1e759bcc27fc4161527952795279587a587a587a757575557a557a557a757575616155007600a26976539f6961946b6c766b796c75a853795379537953007600a26976539f6994618c6b6c766b796c756b7575756c87696155517600a26976539f6961946b6c766b796c75a853795379537953517600a26976539f6994618c6b6c766b796c756b7575756c87696155527600a26976539f6961946b6c766b796c75a853795379537953527600a26976539f6994618c6b6c766b796c756b7575756c876951777777777777')

    fromAddress = P2wshAddress.from_script(p2wsh_witness_script)

    toAddress = P2wpkhAddress.from_address("bc1q99dsng0pq93r6t97llcdy3s7xz96qer03f55wz")

    # define valores
    txid = 'f7b0d5c2a1b70a3ce13afe06f867a9f3c60fd73c9756bb4f37a343e9a8f9d4c1'
    vout = 0
    amount = 0.00286275
    fee = 0.00010000

    # cria a entrada da transação do id tx do 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()]))

    # imprime a transação assinada bruta pronta para ser transmitida
    print("\nRaw signed transaction:\n" + tx.serialize())
    print("\nTxId:", tx.get_txid())


if __name__ == "__main__":
    main()
Enter fullscreen mode Exit fullscreen mode

A parte mais significativa do script acima é quando definimos os dados de assinatura, que contém as pré-imagens corretas nas linhas 25 a 27.

Depois de executar o script, obtemos a transação bruta desta forma:

02000000000101c1d4f9a8e943a3374fbb56973cd70fc6f3a967f806fe3ae13c0ab7a1c2d5b0f70000000000ffffffff013337040000000000160014295b09a1e101623d2cbefff0d2461e308ba0646f0420abc57d70dc5e56ac73e2970077adaf94accfb36dac2b40d34f807cdedad0807b20fe4f237f4e51053fa4cebc55ee15ffd9dd654c2e3b928d4a6243d1f1bcb57ca7205776ddb41c760c1965401fc4521531c3db68cf1023ce5a294a201ccfc887bc85fd2901000000201e3457f730d001d0fbe60830bded73df7166afd38ab3ac273e385003094371c0205657aa4c420a961153d539f4501457995fad8d27e17a5ec7ffda449e8b8aab1e20e60d1fb5e71b6327214e2e9503027c3ba44331562a8e1d193a1e759bcc27fc4161527952795279587a587a587a757575557a557a557a757575616155007600a26976539f6961946b6c766b796c75a853795379537953007600a26976539f6994618c6b6c766b796c756b7575756c87696155517600a26976539f6961946b6c766b796c75a853795379537953517600a26976539f6994618c6b6c766b796c756b7575756c87696155527600a26976539f6961946b6c766b796c75a853795379537953527600a26976539f6994618c6b6c766b796c756b7575756c87695177777777777700000000
Enter fullscreen mode Exit fullscreen mode

De novo, podemos transmiti-la simplesmente usando o mesmo link de antes.

Pronto! Implantamos e chamamos com sucesso um contrato inteligente em sCrypt na blockchain BTC.

# Script de assinatura

Sérias limitações do BTC

O BTC sofre de várias desvantagens graves para o desenvolvimento de contratos inteligentes completos, principalmente devido a seus muitos opcodes desabilitados e limitações artificialmente impostas nas transações e no tamanho do script.

# Lista de opcodes desabilitados no BTC

Em contrapartida, o BSV realiza o poder totalmente irrestrito dos contratos inteligentes do Bitcoin, ao restaurar todos os opcodes e remover todos os limites artificiais. Houve uma explosão cambriana de contratos inteligentes desenvolvidos, como Prova de Conhecimento Zero, Máquinas Turing, ZK-Rollup e Redes Neurais Profundas. Nada disso é possível em BTC e outras cadeias compatíveis com o Bitcoin.

Como um exemplo completo, o quebra-cabeças de hash multipartidário mencionado acima pode ser otimizado pelo uso de um opcode concatenado OP_CAT, que é reabilitado no BSV, mas ainda desabilitado no BTC. Anteriormente, todos os hashes N tiveram que ser incluídos no script de bloqueio, inchando a transação quando N é grande. Em vez disso, podemos combinar todos os y’s em um único “y”. Por exemplo, quando N é 3, deixamos y = H(H(H(y1) || y2) || y3), no qual || é a concatenação. O novo contrato otimizado é como o seguinte:

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

Na linha 20, a concatenação (+) é utilizada, o que não funciona no BTC.

Conclusão

Demonstramos como um contrato inteligente do sCrypt pode ser executado no BTC. De forma semelhante, pode ser executado em qualquer cadeia compatível com o Script como BCH, LiteCoin e DogeCoin. Devido aos seus limites incapacitantes, ele é executado de maneira extremamente inferior do que no BSV, que é a única cadeia ilimitada e escalável a realizar todo o potencial do sCrypt.

O código completo está aqui.

[1] Mais precisamente, é uma linguagem específica de domínio (DSL - Domain-Specific Language) integrada ao TypeScript.

Esse artigo foi escrito por sCrypt e traduzido por Isabela Curado Nehme. Seu original pode ser lido aqui.

bitcoin #bsv #scrypt #contratosinteligentes

Latest comments (0)