Skip to content

Funções Hashing em Solidity Usando Keccak256

Funções Hashing em Solidity Usando Keccak256

O algoritmo keccak256 (família SHA-3) processa o hash de um input para um output de comprimento fixo. O input pode ser um string de comprimento variável ou um número, mas o resultado será sempre um tipo de dado fixo bytes32. É uma função hash criptográfica de uma via, que não pode ser decodificada de trás para frente. Consiste de 64 caracteres (letras e números) que podem ser expressos como números hexadecimais.

A compilação, ou resultado de um hash não foi feito para ser descriptografado com uma chave como algoritmos de criptografia (ex. criptografia de chave pública). A melhor maneira de recuperar um código hash é verificá-lo como resultado de uma função hash. A metodologia de força bruta poderia ser uma alternativa, mas não é o método mais rápido para resolver um código hash para recuperar a mensagem original. Esse é um meio de proteção contra ataques.   Princípio de Hash Básico

Dado um input de um string, por exemplo “Hello World”, passe para uma função hash usando o keccak256. O resultado seria:

Hello World -> keccak256 ->
592fa743889fc7f92ac2a37bb1f5ba1daf2a5c84741ca0e0061d243a2e6707ba

O string “Hello World” não é o mesmo que “hello world”. Se fazemos um hash “hello world”, teremos um resultado totalmente diferente.

hello world -> keccak256 ->
47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad

A menor modificação ou mudança no string resulta em mudança considerável na compilação do hash. Entretanto, pode-se ter inputs dimensionados aleatoriamente, mas eles sempre resultarão no mesmo output. Pegue, por exemplo, dois strings: “O lobo marrom ligeiro pulou em cima do cachorro preguiçoso” “Olá” Agora vamos fazer um hash em cada string usando o keccak256 e vejamos o resultado (Note: As aspas não fazem parte do input).

The quick brown fox jumped over the lazy dog -> keccak256 ->
a82db2ff0b312da9d856a75ba260e9955f0fe467307cd7793521624d11921365
Hello -> keccak256 ->
06b3dfaec148fb1bb2b066f10ec285e7c9bf402ab32aa78a5d38e34566810cd2

Agora vamos comparar os resultados de cada string.

a82db2ff0b312da9d856a75ba260e9955f0fe467307cd7793521624d11921365
06b3dfaec148fb1bb2b066f10ec285e7c9bf402ab32aa78a5d38e34566810cd2

Como podemos ver, ambos têm um comprimento fixo (64 caracteres) embora um string seja mais comprido que o outro. Agora vamos ver as funções de hash utilizada usando o keccak256 nos smart contracts da Solidity.   Codificando Dados de Input Na Solidity (linguagem de programação utilizada na Ethereum), uma função hash deve primeiro ter o input de dados codificado. Alguém pode dizer que não seja necessário codificar os dados e somente faça um hash dos dados brutos (dados originais) diretamente. Pode-se fazer isso, mas não é a melhor prática principalmente se você quer proteger os dados (o objetivo do hashing em primeiro lugar). A palavra-chave abi.encodePacked é usada com demonstrações para codificação de input de dados. Ela é utilizada com as seguintes condições: Tipos mais curtos que 32 bytes são concatenados diretamente, sem preenchimento ou extensão de assinatura. Tipos dinâmicos são codificados no local e sem o comprimento Elementos do tipo array são preenchidos, mas ainda assim, codificados no local.

abi.encodePacked( <data input> )

Isso significa que tipos dinâmicos são codificados no local sem comprimento enquanto tipos estáticos não serão preenchidos se tiverem menos do que 32 bytes. Por exemplo:

abi.encodePacked("AAAA")
0x41414141

Agora podemos usar nossa função hash utilizando a palavra-chave keccak256 no seguinte método: keccak256(abi.encodePacked( <data input> )) Aqui está um exemplo:

function hash(string memory _string) public pure returns(bytes32) {
    return keccak256(abi.encodePacked(_string));
}

Vamos atribuir um valor para string de “Hash Este String”. O resultado que teremos é:

0x5f82559096154cc5c8b38479da101b29c56995ade10aa89e4940b68ef1002567

Observe o prefixo 0x antes da compilação do hash. Isto faz com que o comprimento do string inteiro tenha 66 caracteres de comprimento. O prefixo 0x é adicionado para indicar que é um string hexadecimal.

A codificação foi feita para dar ao programador mais controle de como os dados poderiam ser codificados. Antes, era o compilador que realizava essa função, mas pode ser problemático porque isso pode causar o que se chama de colisões.   Evitando Colisões Existe também uma técnica mais complexa para fazer a codificação, chamada abi.encode. A função abi.encodePacked foi feita para ser mais simples e mais compacta para codificar dados. A função abi.encode pode ser útil quando vem evitar colisões nas funções hash. Uma colisão pode acontecer quando dois inputs diferentes produzem o mesmo output. Isto pode parecer impossível, mas pode acontecer de forma menos esperada. Pegue por exemplo os seguintes inputs:

(AAA, BBB) -> AAABBB        
(AA, ABBB) -> AAABBB

Eles deveriam ser diferentes um do outro, mas como concatenados como uma única string, eles realmente produzirão o mesmo output. Com o abi.encode, codificar um string resulta em:

abi.encode("AAAA")
0x0000000000000000000000000000000000000000000000000000000000000020
0x0000000000000000000000000000000000000000000000000000000000000004
0x4141414100000000000000000000000000000000000000000000000000000000

É um comprimento de 96 bytes ou 3 palavras. Observe que foram preenchidos zeros enquanto no abi.encodePacked isso não aconteceu. Agora vamos usar a função keccak256 e ver porque as colisões podem acontecer. Primeiro, use abi.encodePacked. Vamos usar dois strings como variáveis (não-estado) _string1 e _string2.

function collisionExample(string memory _string1, string memory _string2)
public pure returns (bytes32) {
    return keccak256(abi.encodePacked(_string1, _string2));
}

Vamos usar um exemplo simples (Exemplo 1): _string1 = AAA _string2 = BBB O resultado quando concatenamos os strings e usamos a função hash é:

0xf6568e65381c5fc6a447ddf0dcb848248282b798b2121d9944a87599b7921358

Agora vamos mudar os valores para o seguinte (Exemplo 2): _string1 = AA _string2 = ABBB Eis o resultado:

0xf6568e65381c5fc6a447ddf0dcb848248282b798b2121d9944a87599b7921358

Obtemos o mesmo resultado, exatamente igual ao do exemplo anterior. A razão disso é que quando se concatena os strings, eles resultam exatamente no mesmo string:

AAABBB

Independente da ordem dos caracteres. Pode-se ter _string1 definido para A e _string2 definido para AABBB e o resultado será o mesmo. Isso é uma colisão e em sistemas de produção isso será um problema. Agora vamos usar a mesma função,mas dessa vez com o abi.encode.

function collisionExample2(string memory _string1, string memory _string2)
public pure returns (bytes32) {
    return keccak256(abi.encode(_string1, _string2));
}

Com o primeiro exemplo (Exemplo 1) o resultado é:

0xd6da8b03238087e6936e7b951b58424ff2783accb08af423b2b9a71cb02bd18b

É bem diferente de quando se usa o abi.encodePacked. Agora, a hora da verdade (Exemplo 2), vai resultar o mesmo hash?

0x54bc5894818c61a0ab427d51905bc936ae11a8111a8b373e53c8a34720957abb

Obtemos um output totalmente diferente que evita a ocorrência da colisão. Se existir a probabilidade de inputs resultarem em outputs que pode causar colisão, é recomendado o uso do abi.encode em vez do abi.encodePacked. Há outras técnicas que os desenvolvedores podem utilizar (ex. adicionar um valor aleatório à string concatenada), mas essa técnica é a mais comum.   Sinopse Hashing é um aspecto importante da segurança criptográfica para carteiras digitais e transações na blockchain. Pode ajudar a criar um valor específico determinístico de um input hash, aplicado a esquemas Commit-Reveal e para assinaturas criptográficas compactas. Usar o keccak256 é apenas um exemplo das várias funções hash. O protocolo Ethereum usa o keccak256 em sua rede com um mecanismo de consenso chamado Ethash. Ele tem um papel importante na produção de blocos e na proteção deles na blockchain.

15 de fevereiro

Esse artigo foi escrito por Vincent Tabora e traduzido por Fátima Lima. Você pode ler o original aqui.