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')
}
}
}
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)
Agora, podemos extrair nosso script de resgate:
console.log(instance.lockingScript.toHex())
Isso irá imprimir o script serializado:
0000018257615179547a75537a537a537a0079537a75527a527a7575615279008763537952795279615179517993517a75517a75619c77777777675279518763537952795279949c7777777767006868
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()
Isso irá imprimir a transação P2WSH serializada e assinada, algo como o que se segue:
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()
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
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')
}
}
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.
Latest comments (0)