Lição: 167: Assinatura de Dados Tipados EIP-712
No universo do Ethereum e no desenvolvimento de contratos inteligentes, segurança e usabilidade são fundamentais. Uma melhoria significativa proposta é o EIP-712, que facilita a assinatura de dados estruturados e seguros fora da cadeia. Nesta aula, vamos explorar a Assinatura de Dados Tipados EIP-712, discutindo seus benefícios, casos de uso e fornecendo exemplos de código.
O que é EIP-712?
O EIP-712 é uma Proposta de Melhoria do Ethereum que introduz um padrão para a assinatura de dados estruturados. Diferente dos métodos tradicionais que simplesmente assinam dados brutos ou mensagens, o EIP-712 permite a assinatura de dados tipados, o que significa que a estrutura dos dados é definida, garantindo que todas as partes envolvidas compreendam exatamente o que está sendo assinado. Isso aprimora a segurança e a usabilidade, reduzindo a probabilidade de que dados indesejados sejam assinados.
Benefícios do EIP-712
- Dados Estruturados: Ao definir a estrutura dos dados, a clareza é aprimorada para todas as partes envolvidas.
- Segurança Aprimorada: As assinaturas são criadas para tipos e campos específicos, minimizando a chance de manipulação maliciosa.
- Interoperabilidade: A verificação automática de tipos promove a compatibilidade entre diferentes aplicações e sistemas.
Componentes Principais do EIP-712
O EIP-712 requer a definição de um separador de domínio e uma mensagem estruturada. O separador de domínio ajuda a proteger contra ataques de repetição, enquanto a mensagem estruturada descreve a estrutura dos dados que está sendo assinada.
Separador de Domínio
O separador de domínio inclui o nome do contrato, a versão, o ID da cadeia e o endereço do contrato de verificação. Isso cria um identificador único para a estrutura de dados.
Estrutura de Dados Tipados
Isso define os campos que você deseja incluir em seus dados assinados. Cada campo deve ter um tipo (por exemplo, string, uint256).
Exemplo de Assinatura de Dados Tipados EIP-712
Vamos criar um exemplo onde definimos uma estrutura para assinar uma mensagem simples contendo informações do usuário.
Passo 1: Defina sua Estrutura de Dados
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
contract MeuContrato is EIP712 {
string private constant DOMINIO_ASSINATURA = "MeuContrato";
string private constant VERSAO_ASSINATURA = "1";
struct InfoUsuario {
address usuario;
uint256 id;
bool ativo;
}
bytes32 private constant TYPEHASH_INFO_USUARIO = keccak256(
"InfoUsuario(address usuario,uint256 id,bool ativo)"
);
constructor() EIP712(DOMINIO_ASSINATURA, VERSAO_ASSINATURA) {}
function obterHashInfoUsuario(InfoUsuario memory infoUsuario) internal pure returns (bytes32) {
return keccak256(abi.encode(
TYPEHASH_INFO_USUARIO,
infoUsuario.usuario,
infoUsuario.id,
infoUsuario.ativo
));
}
}
Passo 2: Crie uma Função para Assinar os Dados
Agora, vamos adicionar uma função para permitir que os usuários assinem suas informações.
function assinarInfoUsuario(InfoUsuario memory infoUsuario) external view returns (bytes32) {
bytes32 structHash = obterHashInfoUsuario(infoUsuario);
bytes32 domainSeparator = _hashDomain();
return _hashTypedDataV4(structHash);
}
Passo 3: Verificando a Assinatura
Para garantir que os dados assinados sejam válidos, precisamos implementar uma função de verificação.
function verificar(
InfoUsuario memory infoUsuario,
uint8 v,
bytes32 r,
bytes32 s
) external view returns (bool) {
bytes32 digest = assinarInfoUsuario(infoUsuario);
address signatario = ecrecover(digest, v, r, s);
return signatario == infoUsuario.usuario; // Supondo que o usuário está assinando suas próprias informações
}
Contrato de Exemplo Completo
Aqui está como tudo se interconecta em um contrato completo.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
contract MeuContrato is EIP712 {
string private constant DOMINIO_ASSINATURA = "MeuContrato";
string private constant VERSAO_ASSINATURA = "1";
struct InfoUsuario {
address usuario;
uint256 id;
bool ativo;
}
bytes32 private constant TYPEHASH_INFO_USUARIO = keccak256(
"InfoUsuario(address usuario,uint256 id,bool ativo)"
);
constructor() EIP712(DOMINIO_ASSINATURA, VERSAO_ASSINATURA) {}
function obterHashInfoUsuario(InfoUsuario memory infoUsuario) internal pure returns (bytes32) {
return keccak256(abi.encode(
TYPEHASH_INFO_USUARIO,
infoUsuario.usuario,
infoUsuario.id,
infoUsuario.ativo
));
}
function assinarInfoUsuario(InfoUsuario memory infoUsuario) external view returns (bytes32) {
bytes32 structHash = obterHashInfoUsuario(infoUsuario);
bytes32 domainSeparator = _hashDomain();
return _hashTypedDataV4(structHash);
}
function verificar(
InfoUsuario memory infoUsuario,
uint8 v,
bytes32 r,
bytes32 s
) external view returns (bool) {
bytes32 digest = assinarInfoUsuario(infoUsuario);
address signatario = ecrecover(digest, v, r, s);
return signatario == infoUsuario.usuario; // Supondo que o usuário está assinando suas próprias informações
}
}
Conclusão
A Assinatura de Dados Tipados EIP-712 é uma ferramenta poderosa para desenvolvedores do Ethereum, aprimorando a segurança e a usabilidade da assinatura de dados fora da cadeia. Ao definir dados estruturados e implementar um separador de domínio, você pode reduzir significativamente a probabilidade de erros e ataques maliciosos ao assinar dados. Este tutorial fornece uma base para implementar o EIP-712 em seus contratos inteligentes, permitindo melhor integridade e clareza nas interações.
Sinta-se à vontade para expandir esse conhecimento e integrar o EIP-712 em seus dApps para melhorar a segurança e a experiência do usuário!