A criação de aplicativos on-chain que usam aleatoriedade é um campo minado. E, no entanto, é superimportante para toda uma gama de casos de uso.
Não é que não existam soluções por aí, existem várias maneiras de obter aleatoriedade em seu aplicativo on-chain. É que todos eles são péssimos.
No entanto, isso não é novidade, uma breve pesquisa no Google produzirá muitos blogs e artigos que exploram os trade-offs envolvidos em várias soluções de aleatoriedade.
Esta postagem fornecerá uma breve visão geral dessas compensações e possíveis soluções alternativas.
Ser capaz de identificar os obstáculos pelos quais os desenvolvedores tiveram que passar (ou quais atalhos eles podem ter tomado) para incluir aleatoriedade nos aplicativos web3 que você está usando é uma habilidade valiosa.
Aleatoriedade na cadeia?
Para ser claro, quando estou falando sobre aleatoriedade na cadeia, estou falando sobre fazer coisas na cadeia onde ninguém sabe qual será o resultado.
Isso é crítico para vários casos de uso de blockchain. Alguns exemplos que me vêm à mente:
- escolher atributos aleatoriamente no momento em que o NFT é cunhado (por exemplo, 20% dos meus NFTs de golfinhos mal-humorados devem estar usando óculos escuros)
- jogos on-chain (por exemplo, dando ao azarão em seu jogo PvP uma pequena chance de vencer a batalha ou abrir caixas de saque que podem conter ativos NFT raros)
- jogos de azar (por exemplo, loterias, jogos de cassino, jogos de cartas)
- protocolos (por exemplo, eleger líderes rotativos, distribuir o rendimento da aposta aleatoriamente)
Ter uma fonte conveniente de aleatoriedade torna mais fácil para os desenvolvedores criar projetos mais justos, envolventes e divertidos.
Coroa, você perde
Imagine um aplicativo on-chain muito simples que permite apostar no cara ou coroa. Você aposta 1 ETH, ele joga uma moeda virtual e, se cair cara, você recebe um pagamento.
Vamos tentar escrever o contrato em Solidity (não é importante entender o código abaixo, então não se preocupe se você não for um desenvolvedor, vamos percorrer os bits relevantes).
O primeiro passo com qualquer novo projeto, é claro, é iniciar o ChatGPT e ver o que acontece. Ele me deu isto na primeira tentativa:
pragma solidity ^0.8.0;
contract CoinFlipGame {
uint256 public constant playCost = 1 ether;
uint256 public constant reward = 1.95 ether;
// Inicializa o contrato com 1000 ETH
constructor() payable {
require(msg.value == 1000 ether, "Contract needs to be seeded with 1000 ETH");
}
// A palavra-chave pagável permite que esta função receba Ether
function play() public payable {
require(msg.value == playCost, "Send exactly 1 ETH to play");
require(address(this).balance >= reward, "Prize pool is not sufficient to cover a win");
// lançamento pseudo-aleatório da moeda, verdadeiro é cara, falso é coroa
// É importante observar que isso não é realmente aleatório e pode ser manipulado por mineradores.
// É fundamental usar uma fonte segura de aleatoriedade em um contrato do mundo real.
bool coinFlip = uint256(blockhash(block.number - 1)) % 2 == 0;
if(coinFlip) {
payable(msg.sender).transfer(reward);
}
}
// Função getter para o prêmio atual
function getPrizePool() public view returns (uint256) {
return address(this).balance;
}
}
Novamente, não se preocupe com o que todo o código aqui está fazendo. A parte que nos interessa é como ele determina o lançamento da moeda:
bool coinFlip = uint256(blockhash(block.number - 1)) % 2 == 0;
Está usando o “blockhash” do bloco anterior. Um número muito longo que é determinado caoticamente a partir do conteúdo do bloco anterior. Se uma transação nesse bloco fosse apenas um pouquinho diferente, você obteria um blockhash completamente diferente.
Se esse número longo for par, o contrato diz que deu cara e te paga. Se fosse ímpar, deu coroa e você perdeu.
O ChatGPT forneceu um comentário generoso acima dessa linha coinflip, avisando-nos de que definitivamente não deveríamos usar esse código na produção.
Esse aviso mostra a dificuldade com a aleatoriedade na cadeia. O ChatGPT não pode fornecer um código robusto para um exemplo trivial porque deseja manter o código simples e não possui informações suficientes sobre as compensações apropriadas para o seu projeto.
A razão pela qual esse código é perigoso, e a maior ameaça à segurança na aleatoriedade on-chain em geral, é que o resultado pode ser conhecido no momento em que o bloco está sendo produzido. Então, por que isso é um problema?
Em cada blockchain existe algum sistema que decide quais transações incluir no próximo bloco. Na Ethereum, são bots “pesquisadores” e “proponentes de blocos”, basicamente um mercado para quem pode construir o bloco mais lucrativo. Na camada 2 geralmente é um “sequenciador”, que pode não ser transparente em como toma suas decisões.
Esse sistema de produção de blocos tem o poder. Ele decide se uma transação continua ou não e, se puder ver qual será o resultado de uma transação aleatória, poderá garantir que inclua apenas aqueles que ele quer que ganhe.
Esta é uma das razões para o aviso do ChatGPT. O blockhash é uma boa fonte de aleatoriedade*, o problema é o tempo. O blockhash anterior já é conhecido no momento em que o próximo bloco é produzido.
Não importa o quão "aleatória" ou livre de viés seja a fonte de dados aleatórios, se o produtor do bloco puder decidir como agir sobre isso, ele terá o controle (e esse controle está potencialmente disponível para o maior lance).
Crédito para Randall Munroe (xkcd.com)
*o outro problema com o blockhash é que ele é determinado pelo conteúdo do bloco anterior, que foi escolhido por um produtor de bloco... Esse produtor de bloco pode continuar ajustando as coisas até obter um blockhash que pode ajudá-los a fazer uma transação lucrativa no seguinte bloco.
Qual é a sua pressa?
Isso nos leva ao mais comum e, na minha opinião, o mais doloroso trade-off pela aleatoriedade segura: adicionar um atraso.
Podemos impor um atraso (em número de blocos) entre o usuário executar uma ação e descobrir o resultado dessa ação.
Então, eles se comprometem com o resultado antes desse intervalo e talvez paguem algum custo adiantado, como a aposta em nosso jogo de cara ou coroa. Essa transação inicial é preservada para sempre na blockchain, tornando muito mais difícil projetar um desfazer ou uma nova tentativa depois de algum tempo. Basicamente, um atraso nos permite dizer “sem volta”.
Qualquer atividade não criptografada na cadeia que use aleatoriedade precisa ou ter um atraso antes do resultado ou esperar que o produtor do bloco não esteja trapaceando.
Esse atraso pode ser uma porcaria para a experiência do usuário.
A maneira como escrevemos o jogo coinflip acima oferece uma vitória ou derrota instantânea quando a transação é finalizada. Fica menos divertido se você precisar esperar de 2 a 3 minutos para descobrir o resultado.
Para alguns aplicativos e jogos, esse atraso os tornaria completamente inutilizáveis.
Oráculos — a melhor prática atual
Um oráculo publica dados fora da cadeia na blockchain para que possam ser usados por contratos inteligentes. Eles são usados para tudo, desde feeds de preços até dados meteorológicos, mas também existem oráculos que podem postar dados aleatórios de alta qualidade na cadeia quando solicitados (por exemplo, Chainlink VRF).
Isso é visto como o padrão-ouro na aleatoriedade inviolável. Os usuários podem ter certeza de que seus resultados são baseados em um número aleatório que foi gerado de maneira justa e não pode ser explorado pelos produtores de blocos.
Eles têm um custo:
- tempo - oráculos de aleatoriedade usam um sistema de solicitação-resposta. Há um atraso forçado entre a transação que solicita a aleatoriedade e o resultado publicado na cadeia. Como mencionamos acima, isso afeta a experiência do usuário.
- complexidade— não é mais um one-liner, você precisa criar uma função on-chain (um callback) que o oráculo chamará quando publicar seu número aleatório e isso adicionará sobrecarga ao seu design. Você precisa ter a certeza de usar o serviço corretamente (consulte as "considerações de segurança") para evitar explorações em potencial.
- taxa — outro custo muito real é a taxa paga pelos pedidos. Acho que é o menos doloroso desses custos, mas isso dependerá da economia do seu projeto.
- dependência— idealmente, os aplicativos blockchain devem funcionar para sempre. Enquanto a cadeia estiver funcionando, você pode usá-los. Se você depende de um oráculo, precisa ter certeza de que ele não mudará ou desligará o serviço.
A segurança é opcional?
Se você não tem muito valor para ser roubado, vale mesmo a pena trancar a porta?
Vou usar a palavra 'ataque' nesta seção para falar sobre qualquer tentativa de prever, influenciar, repetir ou descartar resultados aleatórios em aplicativos on-chain para obter algum tipo de vantagem que o designer não planejou. Talvez 'trapacear' seja uma palavra melhor, mas acho que 'atacar' soa mais dramático.
Esses ataques geralmente envolvem o produtor do bloco de alguma forma. Se o invasor está apenas fazendo um lance em um leilão para produzir o bloco ou se ele tem controle dos produtores de blocos por algum outro meio (por exemplo, executando os próprios validadores ou manipulando o sequenciador em um L2).
O custo desses ataques (para o trapaceiro) dependerá da cadeia em que seu aplicativo está implantado e dos cuidados que você tomou, mas nunca é zero.
Portanto, se você acha que o resultado é de baixo valor em relação às propriedades de segurança da cadeia, pode “se safar” usando aleatoriedade insegura em seu aplicativo.
Essa é uma decisão razoável a ser tomada, desde que você seja honesto consigo mesmo sobre os riscos que está assumindo.
- Se o jogo se tornar um sucesso, alguns desses resultados podem se tornar mais valiosos e valerá a pena serem atacados?
- Se a cadeia em que você está implantando tiver sua segurança enfraquecida porque os validadores estão saindo ou o sequenciador está comprometido, seu aplicativo pode valer a pena ser atacado?
- Existe uma chance de alguém atacar mesmo que custe mais do que ganharia (economicamente irracional)? Talvez porque eles queiram causar danos à reputação ou talvez estejam fazendo isso pelas risadas?
De qualquer forma, acho importante mencionar que às vezes a aleatoriedade “boa o suficiente” provavelmente é boa o suficiente, quando é uma decisão consciente. Mas seria bom se você não tivesse que tomar essa decisão...
Aleatoriedade nativa no Obscuro — It Just Works™
Ok, é hora da autopromoção.
Não quero exagerar, mas acho que Obscuro pode ser uma bala de prata para a aleatoriedade na cadeia. Um almoço grátis, se você quiser. O Santo Graal, talvez.
Com uma linha simples em seu código de Solidity, semelhante à linha no código do protótipo que o ChatGPT nos forneceu acima, os contratos inteligentes podem acessar dados aleatórios seguros no Obscuro.
Essa aleatoriedade está disponível imediatamente no contrato, sem necessidade de tempo de espera. Ele é incluído de uma forma que não pode ser repetida pelo sequenciador e não pode ser previsto com base em qualquer estado anterior da cadeia.
A maior mudança que teríamos que fazer para corrigir o código Coinflip do ChatGPT acima é apenas excluir o aviso de comentário sobre como esse código não é adequado para produção.
(Aviso: as coisas estão prestes a ficar mais simplificadas)
Os detalhes de como a aleatoriedade funciona no Obscuro são muito importantes, é fundamental que a gente confie, mas verifique esse tipo de coisa. Mas esses detalhes também são muito técnicos e estou tentando manter as coisas em alto nível.
Portanto, veremos brevemente como a aleatoriedade do Obscuro funciona aqui, mas eu o encorajo a ficar de olho no blog de aprofundamento sobre o assunto que publicaremos em breve, que aborda os detalhes técnicos de como funciona e por que é seguro.
(Aqui vai…)
Os nós Obscuro só podem ser executados em servidores de enclave seguro. Em alto nível, isso significa que eles são executados em hardware especialmente construído que permite provar que apenas o seu código certificado (que é open source para o Obscuro) está rodando lá.
Ele também protege a memória onde esse código está sendo executado para que, mesmo com acesso de administrador ao servidor, você não possa ver nenhum dado no qual o código está sendo executado.
O resultado disso é que o sequenciador (o nó que produz os blocos para o Obscuro) não pode trapacear. E qualquer tentativa de trapaça seria imediatamente visível para toda a rede.
Ele não tem capacidade de selecionar quais transações incluir em um bloco além da abordagem de ordem de chegada em seu código certificado. Ele também não é capaz de descartar um bloco e criar um substituto (portanto, não pode tentar novamente se o invasor não gostar do resultado).
Além do mais, nenhum usuário pode prever qual será o resultado, porque os números aleatórios são baseados em uma semente aleatória privada que ninguém (nem mesmo os operadores do nó) pode acessar.
O outro benefício que obtemos do hardware de enclave seguro é um chip integrado RNG (geração de número aleatório) que fornece uma semente aleatória de alta qualidade para a aleatoriedade nativa.
Portanto, com a implementação do Obscuro, as compensações de que falamos não são mais um problema:
- tempo - assim como com o protótipo desonesto do ChatGPT acima, o resultado está disponível imediatamente assim que a transação é cunhada sem atraso artificial
- taxa -sem taxas extras para aleatoriedade. No Obscuro, toda transação obtém automaticamente sua própria semente aleatória privada disponibilizada para ela durante a execução (se ela for usada ou não)
- segurança — por causa da criptografia e do hardware de enclave seguro "mágico", podemos estar altamente confiantes de que os invasores não podem prever, influenciar, tentar novamente ou descartar resultados aleatórios
- complexidade — acessar os bytes aleatórios é uma linha única em solidity (desenvolvedores: é tão fácil quanto usar blockhash(), block.difficulty ou block.prevrandao). Não precisamos nos preocupar com retornos de chamada após um atraso, ou hashing em valores diferentes para aumentar a entropia, ou qualquer algoritmo inteligente de revelação de confirmação. O código é trivial.
Isso parece bom demais para ser verdade, e espero que depois de ler este blog você esteja olhando para isso com um ceticismo saudável. Como mencionei, haverá um blog de acompanhamento em um futuro próximo com um mergulho técnico profundo sobre como a aleatoriedade do Obscuro funciona e o que nos permite fazer essas garantias.
E, claro, isso não resolve a aleatoriedade para todos os desenvolvedores de blockchain. O problema (sempre há um problema) é que o Obscuro é seu próprio blockchain de camada 2.
Se seus aplicativos estão vinculados a uma cadeia/ecossistema existente por um motivo ou outro, infelizmente, você precisa navegar pelas compensações que discutimos acima.
Mas se mover seu aplicativo ou iniciar seu próximo projeto no Obscuro for uma opção para sua equipe, todo esse novo espaço de design sem atrito se abrirá!
Obrigado por ficar comigo até aqui. Anunciaremos os blogs técnicos de acompanhamento à medida que forem publicados, portanto, fique de olho se estiver interessado em espiar os bastidores.
Enquanto isso, temos um monte de blogs úteis sobre o Obscuro, privacidade e o cenário web3 de forma mais ampla.
Você pode entrar no nosso discord e siga-nos no Twitter para saber mais sobre a experiência do desenvolvedor no Obscuro e manter-se atualizado com o projeto.
Este artigo foi escrito por Matt Curtis e traduzido por Diogo Jorge. O artigo original pode ser encontrado aqui.
Latest comments (0)