MultisigRecovery.sol
1 pragma solidity >=0.5.0 <0.7.0; 2 3 import "../cryptography/MerkleMultiProof.sol"; 4 import "../cryptography/ECDSA.sol"; 5 import "../account/Signer.sol"; 6 import "../ens/ENS.sol"; 7 import "../ens/ResolverInterface.sol"; 8 9 /** 10 * @notice Select privately other accounts that will allow the execution of actions (ERC-2429 compilant) 11 * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH) 12 * Vitalik Buterin (Ethereum Foundation) 13 */ 14 contract MultisigRecovery { 15 //Needed for EIP-1271 check 16 bytes4 constant internal EIP1271_MAGICVALUE = 0x20c13b0b; 17 //threshold constant 18 uint256 public constant THRESHOLD = 100 * 10^18; 19 //Needed for ENS leafs 20 ENS public ens; 21 //flag for used recoveries (user need to define a different publicHash every execute) 22 mapping(bytes32 => bool) public used; 23 //just used offchain 24 mapping(address => uint256) public nonce; 25 //flag approvals 26 mapping(bytes32 => mapping(bytes32 => bool)) public approved; 27 //storage for pending setup 28 mapping(address => RecoverySet) public pending; 29 //storage for active recovery 30 mapping(address => RecoverySet) public active; 31 32 struct RecoverySet { 33 bytes32 publicHash; 34 uint256 setupDelay; 35 uint256 timestamp; 36 } 37 38 event SetupRequested(address indexed who, uint256 activation); 39 event Activated(address indexed who); 40 event Approved(bytes32 indexed approveHash, bytes32 leaf); 41 event Execution(address indexed who, bool success); 42 43 /** 44 * @param _ens Address of ENS Registry 45 **/ 46 constructor( 47 ENS _ens 48 ) 49 public 50 { 51 ens = _ens; 52 } 53 54 /** 55 * @notice Configure recovery parameters of `msg.sender`. `emit Activated(msg.sender)` if there was no previous setup, or `emit SetupRequested(msg.sender, now()+setupDelay)` when reconfiguring. 56 * @param _publicHash Double hash of executeHash 57 * @param _setupDelay Delay for changes being active 58 */ 59 function setup( 60 bytes32 _publicHash, 61 uint256 _setupDelay 62 ) 63 external 64 { 65 require(!used[_publicHash], "_publicHash already used"); 66 used[_publicHash] = true; 67 address who = msg.sender; 68 RecoverySet memory newSet = RecoverySet(_publicHash, _setupDelay, block.timestamp); 69 if(active[who].publicHash == bytes32(0)){ 70 active[who] = newSet; 71 emit Activated(who); 72 } else { 73 require(pending[who].timestamp == 0 || pending[who].timestamp + active[who].setupDelay < block.timestamp, "Waiting activation"); 74 pending[who] = newSet; 75 emit SetupRequested(who, block.timestamp + active[who].setupDelay); 76 } 77 78 } 79 80 /** 81 * @notice Activate a pending setup of recovery parameters 82 * @param _who address whih ready setupDelay. 83 */ 84 function activate(address _who) 85 external 86 { 87 RecoverySet storage pendingUser = pending[_who]; 88 require(pendingUser.timestamp > 0, "No pending setup"); 89 require(pendingUser.timestamp + active[_who].setupDelay > block.timestamp, "Waiting delay"); 90 active[_who] = pendingUser; 91 delete pending[_who]; 92 emit Activated(_who); 93 } 94 95 /** 96 * @notice Cancels a pending setup to change the recovery parameters 97 */ 98 function cancelSetup() 99 external 100 { 101 RecoverySet storage pendingUser = pending[msg.sender]; 102 require(pendingUser.timestamp > 0, "No pending setup"); 103 require(pendingUser.timestamp + active[msg.sender].setupDelay < block.timestamp, "Waiting activation"); 104 delete pending[msg.sender]; 105 emit SetupRequested(msg.sender, 0); 106 } 107 108 /** 109 * @notice Approves a recovery. This method is important for when the address is an contract and dont implements EIP1271. 110 * @param _approveHash Hash of the recovery call 111 * @param _ensNode if present, the _proof is checked against _ensNode. 112 */ 113 function approve( 114 bytes32 _approveHash, 115 bytes32 _ensNode 116 ) 117 external 118 { 119 approveExecution(msg.sender, _approveHash, _ensNode); 120 } 121 122 /** 123 * @notice Approve a recovery using an ethereum signed message 124 * @param _signer address of _signature processor. if _signer is a contract, must be ERC1271. 125 * @param _approveHash Hash of the recovery call 126 * @param _ensNode if present, the _proof is checked against _ensName. 127 * @param _signature ERC191 signature 128 */ 129 function approvePreSigned( 130 address _signer, 131 bytes32 _approveHash, 132 bytes32 _ensNode, 133 bytes calldata _signature 134 ) 135 external 136 { 137 bytes32 signingHash = ECDSA.toERC191SignedMessage(address(this), abi.encodePacked(_getChainID(), _approveHash, _ensNode)); 138 require(_signer != address(0), "Invalid signer"); 139 require( 140 ( 141 isContract(_signer) && Signer(_signer).isValidSignature(abi.encodePacked(signingHash), _signature) == EIP1271_MAGICVALUE 142 ) || ECDSA.recover(signingHash, _signature) == _signer, 143 "Invalid signature"); 144 approveExecution(_signer, _approveHash, _ensNode); 145 } 146 147 /** 148 * @notice executes an approved transaction revaling publicHash hash, friends addresses and set new recovery parameters 149 * @param _executeHash Seed of `peerHash` 150 * @param _merkleRoot Revealed merkle root 151 * @param _calldest Address will be called 152 * @param _calldata Data to be sent 153 * @param _leafData Pre approved leafhashes and it's weights as siblings ordered by descending weight 154 * @param _proofs parents proofs 155 * @param _proofFlags indexes that select the hashing pairs from calldata `_leafHashes` and `_proofs` and from memory `hashes` 156 */ 157 function execute( 158 bytes32 _executeHash, 159 bytes32 _merkleRoot, 160 address _calldest, 161 bytes calldata _calldata, 162 bytes32[] calldata _leafData, 163 bytes32[] calldata _proofs, 164 bool[] calldata _proofFlags 165 ) 166 external 167 { 168 bytes32 publicHash = active[_calldest].publicHash; 169 require(publicHash != bytes32(0), "Recovery not set"); 170 bytes32 partialReveal = keccak256(abi.encodePacked(_executeHash)); 171 require( 172 publicHash == keccak256( 173 abi.encodePacked(partialReveal, keccak256(abi.encodePacked(_merkleRoot))) 174 ), "merkleRoot or executeHash is not valid" 175 ); 176 bytes32 callHash = keccak256( 177 abi.encodePacked(partialReveal, _calldest, _calldata) 178 ); 179 uint256 weight = 0; 180 bytes32[] memory leafs = new bytes32[](_proofs.length/2); 181 for(uint256 i = 0; weight < THRESHOLD; i++){ 182 bytes32 leafHash = _leafData[i*2]; 183 uint256 leafWeight = uint256(_leafData[(i*2)+1]); 184 leafs[i] = keccak256(abi.encodePacked(leafHash,leafWeight)); 185 bytes32 approveHash = keccak256( 186 abi.encodePacked( 187 leafHash, 188 callHash 189 )); 190 require(approved[leafHash][approveHash], "Hash not approved"); 191 weight += leafWeight; 192 delete approved[leafHash][approveHash]; 193 } 194 require(MerkleMultiProof.verifyMultiProof(_merkleRoot, leafs, _proofs, _proofFlags), "Invalid leafHashes"); 195 nonce[_calldest]++; 196 delete active[_calldest]; 197 delete pending[_calldest]; 198 bool success; 199 (success, ) = _calldest.call(_calldata); 200 emit Execution(_calldest, success); 201 } 202 203 /** 204 * @param _signer address of approval signer 205 * @param _approveHash Hash of the recovery call 206 * @param _ensNode if present, the _proof is checked against _ensNode. 207 */ 208 function approveExecution( 209 address _signer, 210 bytes32 _approveHash, 211 bytes32 _ensNode 212 ) 213 internal 214 { 215 bool isENS = _ensNode != bytes32(0); 216 require( 217 !isENS || ( 218 _signer == ResolverInterface(ens.resolver(_ensNode)).addr(_ensNode) 219 ), 220 "Invalid ENS entry" 221 ); 222 bytes32 leaf = keccak256(isENS ? abi.encodePacked(byte(0x01), _ensNode) : abi.encodePacked(byte(0x00), bytes32(uint256(_signer)))); 223 approved[leaf][_approveHash] = true; 224 emit Approved(_approveHash, leaf); 225 } 226 227 /** 228 * @dev Internal function to determine if an address is a contract 229 * @param _target The address being queried 230 * @return True if `_addr` is a contract 231 */ 232 function isContract(address _target) internal view returns(bool result) { 233 assembly { 234 result := gt(extcodesize(_target), 0) 235 } 236 } 237 238 /** 239 * @notice get network identification where this contract is running 240 */ 241 function _getChainID() internal pure returns (uint256) { 242 uint256 id; 243 assembly { 244 //id := chainid() 245 } 246 return id; 247 } 248 }