Aula 204: Construindo Aleatoriedade Segura em Solidity
No mundo das aplicações descentralizadas (dApps), especialmente em jogos e loterias baseados em blockchain, a aleatoriedade segura é fundamental. A geração de números aleatórios previsíveis pode levar a exploração e manipulação, resultando em uma perda de confiança na aplicação. Nesta aula, vamos explorar como construir aleatoriedade segura em Solidity.
Por que a Aleatoriedade é Importante?
A aleatoriedade desempenha um papel crucial em várias aplicações, como:
- Jogos: Determinando o resultado de um lançamento de dados ou uma distribuição de cartas.
- Loterias: Selecionando um vencedor de um grupo de participantes.
- NFTs: Gerando atributos únicos para ativos digitais.
No entanto, obter aleatoriedade segura é um desafio devido à natureza transparente e determinística das redes blockchain. Podemos aproveitar fontes externas e métodos validados pela comunidade para alcançar aleatoriedade segura.
Métodos para Aleatoriedade Segura
1. Variáveis de Bloco On-Chain
Usar variáveis de bloco (como block.number
, block.timestamp
, etc.) é um método comum, mas inseguro, para aleatoriedade. Embora você possa combiná-las para gerar um número pseudo-aleatório, um minerador malicioso pode manipular o resultado.
Exemplo: Aleatoriedade Insegura usando Variáveis de Bloco
pragma solidity ^0.8.0;
contract AleatoriedadeInsegura {
function obterNumeroAleatorio() public view returns (uint) {
// Combinando variáveis de bloco para aleatoriedade
return uint(keccak256(abi.encodePacked(block.timestamp, block.difficulty, block.number)));
}
}
Problemas:
- Previsível por mineradores.
- Fácil de manipular por atores maliciosos.
2. Oráculos
Para construir aplicações seguras, usar oráculos, como o Chainlink VRF (Função Aleatória Verificável), é uma abordagem preferida. Um oráculo é um serviço off-chain que fornece dados externos a contratos inteligentes.
Exemplo: Aleatoriedade Segura usando Chainlink VRF
pragma solidity ^0.8.0;
import "@chainlink/contracts/src/v0.8/VRFConsumerBase.sol";
contract AleatoriedadeSegura is VRFConsumerBase {
bytes32 internal keyHash;
uint256 internal fee;
uint256 public resultadoAleatorio;
constructor(address _vrfCoordinator, address _linkToken, bytes32 _keyHash)
VRFConsumerBase(_vrfCoordinator, _linkToken)
{
keyHash = _keyHash;
fee = 0.1 * 10 ** 18; // 0.1 LINK
}
// Solicitar número aleatório seguro
function obterNumeroAleatorio() public returns (bytes32 requestId) {
require(LINK.balanceOf(address(this)) >= fee, "Não há LINK suficiente");
return requestRandomness(keyHash, fee);
}
// Função de callback usada pelo Coordenador VRF
function fulfillRandomness(bytes32 requestId, uint256 randomness) internal override {
resultadoAleatorio = randomness;
}
}
Como funciona:
- Este contrato estende
VRFConsumerBase
. - Solicita aleatoriedade do serviço Chainlink VRF e a recupera de forma segura.
- A função de callback
fulfillRandomness
recebe o valor aleatório.
3. Esquema de Compromisso e Revelação
Outro método para obter aleatoriedade segura é usar um esquema de compromisso e revelação. Os participantes enviam um valor hashed de sua escolha aleatória sem revelá-la inicialmente. Uma vez que todos os valores são enviados, eles revelam suas escolhas, e um número aleatório pode ser computado.
Exemplo: Esquema de Compromisso e Revelação
pragma solidity ^0.8.0;
contract CompromissoRevelacao {
struct Participante {
bytes32 compromisso;
uint256 numeroRevelado;
}
mapping(address => Participante) public participantes;
address[] public enderecosParticipantes;
function comprometer(bytes32 _compromisso) public {
participantes[msg.sender].compromisso = _compromisso;
enderecosParticipantes.push(msg.sender);
}
function revelar(uint256 _numero) public {
Participante storage participante = participantes[msg.sender];
require(participante.compromisso != bytes32(0), "Você deve se comprometer primeiro.");
// Verificar se o compromisso corresponde ao número revelado
require(participante.compromisso == keccak256(abi.encodePacked(_numero)), "Revelação inválida.");
participante.numeroRevelado = _numero;
}
function obterNumeroAleatorio() public view returns (uint256) {
uint256 total = 0;
for (uint256 i = 0; i < enderecosParticipantes.length; i++) {
total += participantes[enderecosParticipantes[i]].numeroRevelado;
}
return uint256(keccak256(abi.encodePacked(total))) % 100; // Número aleatório [0, 99]
}
}
Como funciona:
- Os participantes se comprometem com suas escolhas aleatórias usando o hash.
- Após todas as compromissos serem coletados, os participantes revelam seus números.
- O contrato então calcula um número aleatório usando os valores revelados.
Conclusão
A aleatoriedade segura é vital para manter a integridade de várias aplicações na blockchain. Desde a utilização de oráculos externos como o Chainlink VRF até a implementação de esquemas de compromisso e revelação, os desenvolvedores devem ser cautelosos ao projetar soluções de aleatoriedade. Sempre avalie as implicações de segurança dos métodos que você escolhe para garantir confiança e justiça em suas dApps.