Nesse artigo iremos abordar sobre como verificar uma assinatura hashing através da função keccak256
.
Hashing com Keccak256
O keccack256
é uma função nativa do Solidity que calcula o hash de entrada Keccak-256.
Alguns casos de uso são:
- Criar um ID exclusivo determinístico de uma entrada
- Esquema de confirmação-revelação
- Assinatura criptografada compacta (assinando o hash em vez de uma entrada maior)
// SPDX-License-Identifier: MITpragma solidity ^0.8.13;contract HashFunction {function hash(string memory _text,uint _num,address _addr) public pure returns (bytes32) {return keccak256(abi.encodePacked(_text, _num, _addr));}// Exemplo de colisão de hash// A colisão de hash pode ocorrer quando você passa mais de um tipo de dados dinâmico// para abi.encodePacked. Nesse caso, você deve usar abi.encode.function collision(string memory _text, string memory _anotherText)publicpurereturns (bytes32){// encodePacked(AAA, BBB) -> AAABBB// encodePacked(AA, ABBB) -> AAABBBreturn keccak256(abi.encodePacked(_text, _anotherText));}}contract GuessTheMagicWord {bytes32 public answer =0x60298f78cc0b47170ba79c10aa3851d7648bd96f2f8e46a19dbc777c36fb0c00;// Palavra mágica é "Solidity"function guess(string memory _word) public view returns (bool) {return keccak256(abi.encodePacked(_word)) == answer;}}
Verificando uma assinatura
As mensagens podem ser assinadas fora da blockchain e depois verificadas dentro da blockchain usando um contrato inteligente.
Aqui um exemplo de verificação da assinatura assinada fora do contrato e verificado na chamada de uma função do contrato.
// SPDX-License-Identifier: MITpragma solidity ^0.8.13;/* Verificação de assinaturaComo assinar e verificar# Assinatura1. Crie uma mensagem para assinar2. Faça o hash da mensagem3. Assine o hash (fora da blockchain, mantenha sua chave privada em segredo)# Verificar1. Recrie o hash da mensagem original2. Recupere o signatário da assinatura e do hash3. Compare o signatário recuperado com o signatário reivindicado*/contract VerifySignature {/* 1. Desbloqueie sua conta MetaMaskethereum.enable()*//* 2. Obtém a mensagem com o código hash de assinaturagetMessageHash(0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C,123,"coffee and donuts",1)hash = "0xcf36ac4f97dc10d91fc2cbb20d718e94a8cbfe0f82eaedc6a4aa38946fb797cd"*/function getMessageHash(address _to,uint _amount,string memory _message,uint _nonce) public pure returns (bytes32) {return keccak256(abi.encodePacked(_to, _amount, _message, _nonce));}/* 3. Assina o contrato com o hash# usando um navegadoraccount = "copie e cole a conta do signatário aqui"ethereum.request({ method: "personal_sign", params: [account, hash]}).then(console.log)# usando a web3web3.personal.sign(hash, web3.eth.defaultAccount, console.log)A assinatura será diferente para contas diferentes0x993dab3dd91f5c6dc28e17439be475478f5635c92a56e17e82349d3fb2f166196f466c0b4e0c146f285204f0dcb13e5ae67bc33f4b888ec32dfe0a063e8f3f781b*/function getEthSignedMessageHash(bytes32 _messageHash)publicpurereturns (bytes32){/*A assinatura é produzida assinando um hash keccak256 com o seguinte formato:"\x19Ethereum Signed Message\n" + len(msg) + msg*/returnkeccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", _messageHash));}/* 4. Verifica a assinaturasigner = 0xB273216C05A8c0D4F0a4Dd0d7Bae1D2EfFE636ddto = 0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160Camount = 123message = "coffee and donuts"nonce = 1signature =0x993dab3dd91f5c6dc28e17439be475478f5635c92a56e17e82349d3fb2f166196f466c0b4e0c146f285204f0dcb13e5ae67bc33f4b888ec32dfe0a063e8f3f781b*/function verify(address _signer,address _to,uint _amount,string memory _message,uint _nonce,bytes memory signature) public pure returns (bool) {bytes32 messageHash = getMessageHash(_to, _amount, _message, _nonce);bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash);return recoverSigner(ethSignedMessageHash, signature) == _signer;}function recoverSigner(bytes32 _ethSignedMessageHash, bytes memory _signature)publicpurereturns (address){(bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature);return ecrecover(_ethSignedMessageHash, v, r, s);}function splitSignature(bytes memory sig)publicpurereturns (bytes32 r,bytes32 s,uint8 v){require(sig.length == 65, "Tamanho de assinatura inválido");assembly {/*Os primeiros 32 bytes armazenam o comprimento da assinaturaadd(sig, 32) = ponteiro da assinatura + 32efetivamente, pula os primeiros 32 bytes da assinaturamload(p) carrega os próximos 32 bytes começando no endereço de memória p na memória*/// primeiros 32 bytes, após o prefixo de comprimentor := mload(add(sig, 32))// dos 32 até os 64 bytess := mload(add(sig, 64))// últimos 32 byte (primeiro byte dos próximos 32 bytes)v := byte(0, mload(add(sig, 96)))}// implicitly return (r, s, v)}}
Aqui um exemplo de implementação de uma assinatura sendo feita fora do contrato utilizando javascript
.
const { expect } = require("chai")const { ethers } = require("hardhat")describe("VerifySignature", function () {it("Check signature", async function () {const accounts = await ethers.getSigners(2)const VerifySignature = await ethers.getContractFactory("VerifySignature")const contract = await VerifySignature.deploy()await contract.deployed()// const PRIV_KEY = "0x..." - Private Key da MetaMask (nunca revele para ninguém essa key)// const signer = new ethers.Wallet(PRIV_KEY)const signer = accounts[0]const to = accounts[1].addressconst amount = 999const message = "Hello"const nonce = 123const hash = await contract.getMessageHash(to, amount, message, nonce)const sig = await signer.signMessage(ethers.utils.arrayify(hash))const ethHash = await contract.getEthSignedMessageHash(hash)console.log("signatário ", signer.address)console.log("signatário recuperado", await contract.recoverSigner(ethHash, sig))// Mensagem com assinatura correta irá retornar trueexpect(await contract.verify(signer.address, to, amount, message, nonce, sig)).to.equal(true)// Mensagem com assinatura incorreta irá retornar falseexpect(await contract.verify(signer.address, to, amount + 1, message, nonce, sig)).to.equal(false)})})
Para quais casos de uso isso serviria?
- realizar transações de jogos NFT
- criação e transação de itens NFT
- criação de um marketplace
- sistema de airdrops e staking
Entre várias outras utilidades.