오프체인 상태에서 메시지에 서명을 하고, 함수를 실행하기 전에 해당 서명을 요구하는 계약을 체결하는 것은 유용한 기술이다.
예를 들어 체인 위에서 이루어지는 트랜잭션의 수를 줄인다.
또한 메타 트랜잭션이라 부르는 가스비가 없는 트랜잭션을 생성한다.
하지만 동일한 서명은 함수를 실행시키기 위해 여러번 사용될 수 있다.
이것은 서명자의 의도가 단지 한 번의 거래를 승인하는 것이었다면 해로울 수 있다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
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;
constructor(address[2] memory _owners) payable {
owners = _owners;
}
function deposit() external payable {}
// transfer 함수는 **소유자의 동일한 서명을 이용해 여러번 실행이 가능**하다.
function transfer(address _to, uint _amount, bytes[2] memory _sigs) external {
bytes32 txHash = getTxHash(_to, _amount);
require(_checkSigs(_sigs, txHash), "Invalid Sig");
(bool sent, ) = _to.call{value: _amount}("");
require(sent, "Failed");
}
function getTxHash(address _to, uint _amount) public view returns (bytes32) {
return keccak256(abi.encodePacked(_to, _amount));
}
function _checkSigs(bytes[2] memory _sigs, bytes32 _txHash) private view returns (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;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
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 {}
// **transfer 함수에 nonce 값을 추가**한다.
function transfer(address _to, uint _amount, **uint _nonce**, bytes[2] memory _sigs) external {
bytes32 txHash = getTxHash(_to, _amount, **_nonce**);
require(!executed[txHash]);
require(_checkSigs(_sigs, txHash), "Invalid Sig");
executed[txHash] = true;
(bool sent, ) = _to.call{value: _amount}("");
require(sent, "Failed");
}
// **txHash 값에 address(this) 와 nonce 를 추가**한다.
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) private view returns (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;
}
}