Ao contrário da crença popular, o Bitcoin¹ vem com capacidade de contratação inteligente desde seu início, com uma linguagem de programação nativa baseada em pilha, chamada Script. Cada transação em Bitcoin consiste de entradas e saídas. Cada saída bloqueia alguns bitcoins com um script que dita as obrigações de um contrato. Se uma entrada vem com um script que cumpre o contrato de uma saída, ele desbloqueia bitcoins² nessa saída e os move para novas saídas. É assim que funciona a transferência de propriedade de bitcoins.
O script é geralmente considerado como extremamente limitado e, portanto, incapaz de sofisticadas contratações inteligentes. Uma deficiência frequentemente citada da contratação inteligente de Bitcoin é sua falta de estado. É uma limitação importante que o Ethereum supõe superar e que justifica sua existência.
Pré-requisito: OP_PUSH_TX
Antes de analisarmos como manter o estado nos contratos inteligentes Bitcoin, introduzimos uma técnica poderosa chamada OP_PUSH_TX. Ela pode ser considerada como um pseudo opcode³ que empurra a transação atual para a pilha, que pode então ser inspecionada em tempo de execução. Mais precisamente, ela permite a inspeção da pré-imagem utilizada na verificação da assinatura definida no BIP143. O formato da pré-imagem é o seguinte:
Implementação de contratos com estado
Uma vez que podemos inspecionar o contexto da transação de um contrato, podemos colocar restrições arbitrárias em suas entradas e saídas.
Uma maneira de implementar o estado em contrato é dividir o contrato no roteiro de bloqueio em duas partes: dados e código. A parte de dados é o estado. A parte de código contém a lógica comercial de um contrato que codifica regras para a transição do estado. Os dados são anexados passivamente ao código como OP_RETURN <dados> ou OP_PUSHDATA <dados> OP_DROP. Mesmo não sendo avaliados, ainda afetam a validade de um contrato, uma vez que a parte de código anterior o valida.
Usando OP_PUSH_TX, podemos obter o roteiro de travamento da saída sendo gasta da parte 5 e o da nova saída da parte 8. Para manter o estado, exigimos que a parte de código do script de travamento não mude e que as mudanças de dados/estado cumpram as regras de transição de estado na parte de código. Isto é análogo ao conceito de objeto na Programação Orientada a Objetos, sendo o código métodos e dados variáveis membros de um objeto. Os métodos são imutáveis. As variáveis de membros são encapsuladas e só podem ser alteradas através de métodos⁵. Os métodos são chamados a partir do script de desbloqueio, codificando o método a ser chamado e seus argumentos.
Um exemplo de contrato: Contador
Vejamos um exemplo simples de um contrato com estado: um contra-contrato que rastreia quantas vezes sua função increment() foi chamada. Seu código é mostrado abaixo com comentários em linha.
contract Counter {
public function increment(SigHashPreimage txPreimage, int amount) {
require(Tx.checkPreimage(txPreimage));
// deserializar o estado (i.e., valor do contador)
bytes scriptCode = Util.scriptCode(txPreimage);
int scriptLen = len(scriptCode);
// contador está no final
int counter = unpack(scriptCode[scriptLen - Util.DataLen :]);
// incrementa o contador
counter++;
// serializa o estado
bytes outputScript = scriptCode[: scriptLen - Util.DataLen] + num2bin(counter, Util.DataLen);
bytes output = Util.buildOutput(outputScript, amount);
// garantir que a produção seja esperada: a quantidade é a mesma especificada
// também o script de saída é o mesmo com o scriptCode, exceto o contador incrementado
require(hash256(output) == Util.hashOutputs(txPreimage));
}
}
A linha 3 garante que a pré-imagem é da transação atual. Recebemos o script de bloqueio anterior na Linha 6, também conhecido como scriptCode na parte 5 da pré-imagem. O estado do contador anterior é extraído do scriptCode na Linha 9, após o qual é incrementado e colocado no novo script de travamento na Linha 15. Observe que o contador é a única parte que muda no script de travamento. O restante garante que a saída contenha o novo script de travamento.
Aqui está o código para implantar o contrato e chamar repetidamente a função increment(). Uma instância do contrato com incremento de contador de 0 a 9 pode ser encontrada: 0 -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9. Observe que o estado do contador está no final do script da primeira saída da transação.
Conclusão
Isto é parte de uma série que estamos escrevendo para demonstrar o que os contratos inteligentes Bitcoin podem fazer e como implementá-los. Muitas supostas limitações do Script são devidas ao fracasso na realização de seu potencial. Como explicamos e demonstramos mais sobre o que o Script pode alcançar, as pessoas vão achar que ele é extremamente extensível, versátil e orientado para o futuro. Mostraremos que o Bitcoin, sem limites artificiais, pode executar qualquer contrato inteligente que outras blockchains possam executar, ao mesmo tempo em que é capaz de escalonamento ilimitado. Ela pode ser alavancada para tornar muitas aplicações em todas as indústrias mais eficientes e seguras através de incentivos econômicos.
Agradecimentos
Agradecimentos especiais vão para nChain por fornecer a idéia original de OP_PUSH_TX. Agradecemos também a George Papageorgiou, James Belding, Brenton Gunning e Joel Dalais pelas valiosas sugestões sobre uma versão anterior deste artigo.
[1]: No artigo, nos referimos ao Bitcoin SV como Bitcoin, pois segue o desenho original do protocolo.
[2]: Existe uma exceção que algumas saídas contêm zero bitcoins, marcados por, por exemplo, OP_RETURN.
[3]: Um opcode é a unidade básica do script.
[4]: Há alguma exceção a esta regra geral em algumas linguagens dinâmicas do OOP.
[5]: Há também algumas exceções a esta regra geral.
Artigo escrito por sCrypt. A versão original pode ser encontrada aqui. Traduzido e adaptado por Dimitris Calixto.
Top comments (0)