Nesse artigo iremos aprender a como um contrato malicioso consegue utilizar da técnica de repetição de assinatura para burlar um contrato inteligente.
Repetição de assinatura
Assinar mensagens fora da blockchain e ter um contrato que exija essa assinatura antes de executar uma função é uma técnica útil.
Por exemplo, esta técnica é usada para:
- reduzir o número de transações na blockchain
- transação sem gas, chamada de
meta transaction
Vulnerabilidade
A mesma assinatura pode ser usada várias vezes para executar uma função. Isso pode ser prejudicial se a intenção do signatário for aprovar uma transação uma vez.
// SPDX-License-Identifier: MITpragma solidity ^0.8.13;pragma experimental ABIEncoderV2;import "github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v4.5/contracts/utils/cryptography/ECDSA.sol";// Carteira com multi assinaturascontract MultiSigWallet {using ECDSA for bytes32;address[2] public owners;constructor(address[2] memory _owners) payable {owners = _owners;}function deposit() external payable {}function transfer(address _to,uint _amount,bytes[2] memory _sigs) external {bytes32 txHash = getTxHash(_to, _amount);require(_checkSigs(_sigs, txHash), "Assinatura inválida");(bool sent, ) = _to.call{value: _amount}("");require(sent, "Falha ao enviar Ether");}function getTxHash(address _to, uint _amount) public view returns (bytes32) {return keccak256(abi.encodePacked(_to, _amount));}function _checkSigs(bytes[2] memory _sigs, bytes32 _txHash)privateviewreturns (bool){bytes32 ethSignedHash = _txHash.toEthSignedMessageHash();for (uint i = 0; i < _sigs.length; i++) {address signer = ethSignedHash.recover(_sigs[i]);bool valid = signer == owners[i];if (!valid) {return false;}}return true;}}
Técnicas preventivas
Assine mensagens com nonce
e endereço do contrato.
// SPDX-License-Identifier: MITpragma solidity ^0.8.13;pragma experimental ABIEncoderV2;import "github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v4.5/contracts/utils/cryptography/ECDSA.sol";contract MultiSigWallet {using ECDSA for bytes32;address[2] public owners;mapping(bytes32 => bool) public executed;constructor(address[2] memory _owners) payable {owners = _owners;}function deposit() external payable {}function transfer(address _to,uint _amount,uint _nonce,bytes[2] memory _sigs) external {bytes32 txHash = getTxHash(_to, _amount, _nonce);require(!executed[txHash], "transação executada");require(_checkSigs(_sigs, txHash), "assinatura inválida");executed[txHash] = true;(bool sent, ) = _to.call{value: _amount}("");require(sent, "Falha ao enviar Ether");}function getTxHash(address _to,uint _amount,uint _nonce) public view returns (bytes32) {return keccak256(abi.encodePacked(address(this), _to, _amount, _nonce));}function _checkSigs(bytes[2] memory _sigs, bytes32 _txHash)privateviewreturns (bool){bytes32 ethSignedHash = _txHash.toEthSignedMessageHash();for (uint i = 0; i < _sigs.length; i++) {address signer = ethSignedHash.recover(_sigs[i]);bool valid = signer == owners[i];if (!valid) {return false;}}return true;}}/*// proprietários0xe19aea93F6C1dBef6A3776848bE099A7c3253ac80xfa854FE5339843b3e9Bfd8554B38BD042A42e340// para0xe10422cc61030C8B3dBCD36c7e7e8EC3B527E0Ac// quantia100// nonce0// hash da transação0x12a095462ebfca27dc4d99feef885bfe58344fb6bb42c3c52a7c0d6836d11448// assinaturas0x120f8ed8f2fa55498f2ef0a22f26e39b9b51ed29cc93fe0ef3ed1756f58fad0c6eb5a1d6f3671f8d5163639fdc40bb8720de6d8f2523077ad6d1138a60923b801c0xa240a487de1eb5bb971e920cb0677a47ddc6421e38f7b048f8aa88266b2c884a10455a52dc76a203a1a9a953418469f9eec2c59e87201bbc8db0e4d9796935cb1b*/